1 /**
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0.
4 */
5
6 #include <aws/io/stream.h>
7
8 #include <aws/common/file.h>
9 #include <aws/io/file_utils.h>
10
11 #include <errno.h>
12
aws_input_stream_seek(struct aws_input_stream * stream,int64_t offset,enum aws_stream_seek_basis basis)13 int aws_input_stream_seek(struct aws_input_stream *stream, int64_t offset, enum aws_stream_seek_basis basis) {
14 AWS_ASSERT(stream && stream->vtable && stream->vtable->seek);
15
16 return stream->vtable->seek(stream, offset, basis);
17 }
18
aws_input_stream_read(struct aws_input_stream * stream,struct aws_byte_buf * dest)19 int aws_input_stream_read(struct aws_input_stream *stream, struct aws_byte_buf *dest) {
20 AWS_ASSERT(stream && stream->vtable && stream->vtable->read);
21 AWS_ASSERT(dest);
22 AWS_ASSERT(dest->len <= dest->capacity);
23
24 /* Deal with this edge case here, instead of relying on every implementation to do it right. */
25 if (dest->capacity == dest->len) {
26 return AWS_OP_SUCCESS;
27 }
28
29 /* Prevent implementations from accidentally overwriting existing data in the buffer.
30 * Hand them a "safe" buffer that starts where the existing data ends. */
31 const void *safe_buf_start = dest->buffer + dest->len;
32 const size_t safe_buf_capacity = dest->capacity - dest->len;
33 struct aws_byte_buf safe_buf = aws_byte_buf_from_empty_array(safe_buf_start, safe_buf_capacity);
34
35 int read_result = stream->vtable->read(stream, &safe_buf);
36
37 /* Ensure the implementation did not commit forbidden acts upon the buffer */
38 AWS_FATAL_ASSERT(
39 (safe_buf.buffer == safe_buf_start) && (safe_buf.capacity == safe_buf_capacity) &&
40 (safe_buf.len <= safe_buf_capacity));
41
42 if (read_result == AWS_OP_SUCCESS) {
43 /* Update the actual buffer */
44 dest->len += safe_buf.len;
45 }
46
47 return read_result;
48 }
49
aws_input_stream_get_status(struct aws_input_stream * stream,struct aws_stream_status * status)50 int aws_input_stream_get_status(struct aws_input_stream *stream, struct aws_stream_status *status) {
51 AWS_ASSERT(stream && stream->vtable && stream->vtable->get_status);
52
53 return stream->vtable->get_status(stream, status);
54 }
55
aws_input_stream_get_length(struct aws_input_stream * stream,int64_t * out_length)56 int aws_input_stream_get_length(struct aws_input_stream *stream, int64_t *out_length) {
57 AWS_ASSERT(stream && stream->vtable && stream->vtable->get_length);
58
59 return stream->vtable->get_length(stream, out_length);
60 }
61
aws_input_stream_destroy(struct aws_input_stream * stream)62 void aws_input_stream_destroy(struct aws_input_stream *stream) {
63 if (stream != NULL) {
64 AWS_ASSERT(stream->vtable && stream->vtable->destroy);
65
66 stream->vtable->destroy(stream);
67 }
68 }
69
70 /*
71 * cursor stream implementation
72 */
73
74 struct aws_input_stream_byte_cursor_impl {
75 struct aws_byte_cursor original_cursor;
76 struct aws_byte_cursor current_cursor;
77 };
78
79 /*
80 * This is an ugly function that, in the absence of better guidance, is designed to handle all possible combinations of
81 * size_t (uint32_t, uint64_t). If size_t ever exceeds 64 bits this function will fail badly.
82 *
83 * Safety and invariant assumptions are sprinkled via comments. The overall strategy is to cast up to 64 bits and
84 * perform all arithmetic there, being careful with signed vs. unsigned to prevent bad operations.
85 *
86 * Assumption #1: size_t resolves to an unsigned integer 64 bits or smaller
87 */
88
89 AWS_STATIC_ASSERT(sizeof(size_t) <= 8);
90
s_aws_input_stream_byte_cursor_seek(struct aws_input_stream * stream,int64_t offset,enum aws_stream_seek_basis basis)91 static int s_aws_input_stream_byte_cursor_seek(
92 struct aws_input_stream *stream,
93 int64_t offset,
94 enum aws_stream_seek_basis basis) {
95 struct aws_input_stream_byte_cursor_impl *impl = stream->impl;
96
97 uint64_t final_offset = 0;
98
99 switch (basis) {
100 case AWS_SSB_BEGIN:
101 /*
102 * (uint64_t)offset -- safe by virtue of the earlier is-negative check
103 * (uint64_t)impl->original_cursor.len -- safe via assumption 1
104 */
105 if (offset < 0 || (uint64_t)offset > (uint64_t)impl->original_cursor.len) {
106 return aws_raise_error(AWS_IO_STREAM_INVALID_SEEK_POSITION);
107 }
108
109 /* safe because negative offsets were turned into an error */
110 final_offset = (uint64_t)offset;
111 break;
112
113 case AWS_SSB_END:
114 /*
115 * -offset -- safe as long offset is not INT64_MIN which was previously checked
116 * (uint64_t)(-offset) -- safe because (-offset) is positive (and < INT64_MAX < UINT64_MAX)
117 */
118 if (offset > 0 || offset == INT64_MIN || (uint64_t)(-offset) > (uint64_t)impl->original_cursor.len) {
119 return aws_raise_error(AWS_IO_STREAM_INVALID_SEEK_POSITION);
120 }
121
122 /* cases that would make this unsafe became errors with previous conditional */
123 final_offset = (uint64_t)impl->original_cursor.len - (uint64_t)(-offset);
124 break;
125
126 default:
127 return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
128 }
129
130 /* true because we already validated against (impl->original_cursor.len) which is <= SIZE_MAX */
131 AWS_ASSERT(final_offset <= SIZE_MAX);
132
133 /* safe via previous assert */
134 size_t final_offset_sz = (size_t)final_offset;
135
136 /* sanity */
137 AWS_ASSERT(final_offset_sz <= impl->original_cursor.len);
138
139 /* reset current_cursor to new position */
140 impl->current_cursor = impl->original_cursor;
141 impl->current_cursor.ptr += final_offset_sz;
142 impl->current_cursor.len -= final_offset_sz;
143
144 return AWS_OP_SUCCESS;
145 }
146
s_aws_input_stream_byte_cursor_read(struct aws_input_stream * stream,struct aws_byte_buf * dest)147 static int s_aws_input_stream_byte_cursor_read(struct aws_input_stream *stream, struct aws_byte_buf *dest) {
148 struct aws_input_stream_byte_cursor_impl *impl = stream->impl;
149
150 size_t actually_read = dest->capacity - dest->len;
151 if (actually_read > impl->current_cursor.len) {
152 actually_read = impl->current_cursor.len;
153 }
154
155 if (!aws_byte_buf_write(dest, impl->current_cursor.ptr, actually_read)) {
156 return aws_raise_error(AWS_IO_STREAM_READ_FAILED);
157 }
158
159 aws_byte_cursor_advance(&impl->current_cursor, actually_read);
160
161 return AWS_OP_SUCCESS;
162 }
163
s_aws_input_stream_byte_cursor_get_status(struct aws_input_stream * stream,struct aws_stream_status * status)164 static int s_aws_input_stream_byte_cursor_get_status(
165 struct aws_input_stream *stream,
166 struct aws_stream_status *status) {
167 struct aws_input_stream_byte_cursor_impl *impl = stream->impl;
168
169 status->is_end_of_stream = impl->current_cursor.len == 0;
170 status->is_valid = true;
171
172 return AWS_OP_SUCCESS;
173 }
174
s_aws_input_stream_byte_cursor_get_length(struct aws_input_stream * stream,int64_t * out_length)175 static int s_aws_input_stream_byte_cursor_get_length(struct aws_input_stream *stream, int64_t *out_length) {
176 struct aws_input_stream_byte_cursor_impl *impl = stream->impl;
177
178 #if SIZE_MAX > INT64_MAX
179 size_t length = impl->original_cursor.len;
180 if (length > INT64_MAX) {
181 return aws_raise_error(AWS_ERROR_OVERFLOW_DETECTED);
182 }
183 #endif
184
185 *out_length = (int64_t)impl->original_cursor.len;
186
187 return AWS_OP_SUCCESS;
188 }
189
s_aws_input_stream_byte_cursor_destroy(struct aws_input_stream * stream)190 static void s_aws_input_stream_byte_cursor_destroy(struct aws_input_stream *stream) {
191 aws_mem_release(stream->allocator, stream);
192 }
193
194 static struct aws_input_stream_vtable s_aws_input_stream_byte_cursor_vtable = {
195 .seek = s_aws_input_stream_byte_cursor_seek,
196 .read = s_aws_input_stream_byte_cursor_read,
197 .get_status = s_aws_input_stream_byte_cursor_get_status,
198 .get_length = s_aws_input_stream_byte_cursor_get_length,
199 .destroy = s_aws_input_stream_byte_cursor_destroy};
200
aws_input_stream_new_from_cursor(struct aws_allocator * allocator,const struct aws_byte_cursor * cursor)201 struct aws_input_stream *aws_input_stream_new_from_cursor(
202 struct aws_allocator *allocator,
203 const struct aws_byte_cursor *cursor) {
204
205 struct aws_input_stream *input_stream = NULL;
206 struct aws_input_stream_byte_cursor_impl *impl = NULL;
207
208 aws_mem_acquire_many(
209 allocator,
210 2,
211 &input_stream,
212 sizeof(struct aws_input_stream),
213 &impl,
214 sizeof(struct aws_input_stream_byte_cursor_impl));
215
216 if (!input_stream) {
217 return NULL;
218 }
219
220 AWS_ZERO_STRUCT(*input_stream);
221 AWS_ZERO_STRUCT(*impl);
222
223 input_stream->allocator = allocator;
224 input_stream->vtable = &s_aws_input_stream_byte_cursor_vtable;
225 input_stream->impl = impl;
226
227 impl->original_cursor = *cursor;
228 impl->current_cursor = *cursor;
229
230 return input_stream;
231 }
232
233 /*
234 * file-based input stream
235 */
236 struct aws_input_stream_file_impl {
237 FILE *file;
238 bool close_on_clean_up;
239 };
240
s_aws_input_stream_file_seek(struct aws_input_stream * stream,int64_t offset,enum aws_stream_seek_basis basis)241 static int s_aws_input_stream_file_seek(
242 struct aws_input_stream *stream,
243 int64_t offset,
244 enum aws_stream_seek_basis basis) {
245 struct aws_input_stream_file_impl *impl = stream->impl;
246
247 int whence = (basis == AWS_SSB_BEGIN) ? SEEK_SET : SEEK_END;
248 if (aws_fseek(impl->file, offset, whence)) {
249 return AWS_OP_ERR;
250 }
251
252 return AWS_OP_SUCCESS;
253 }
254
s_aws_input_stream_file_read(struct aws_input_stream * stream,struct aws_byte_buf * dest)255 static int s_aws_input_stream_file_read(struct aws_input_stream *stream, struct aws_byte_buf *dest) {
256 struct aws_input_stream_file_impl *impl = stream->impl;
257
258 size_t max_read = dest->capacity - dest->len;
259 size_t actually_read = fread(dest->buffer + dest->len, 1, max_read, impl->file);
260 if (actually_read == 0) {
261 if (ferror(impl->file)) {
262 return aws_raise_error(AWS_IO_STREAM_READ_FAILED);
263 }
264 }
265
266 dest->len += actually_read;
267
268 return AWS_OP_SUCCESS;
269 }
270
s_aws_input_stream_file_get_status(struct aws_input_stream * stream,struct aws_stream_status * status)271 static int s_aws_input_stream_file_get_status(struct aws_input_stream *stream, struct aws_stream_status *status) {
272 struct aws_input_stream_file_impl *impl = stream->impl;
273
274 status->is_end_of_stream = feof(impl->file) != 0;
275 status->is_valid = ferror(impl->file) == 0;
276
277 return AWS_OP_SUCCESS;
278 }
279
s_aws_input_stream_file_get_length(struct aws_input_stream * stream,int64_t * length)280 static int s_aws_input_stream_file_get_length(struct aws_input_stream *stream, int64_t *length) {
281 struct aws_input_stream_file_impl *impl = stream->impl;
282
283 return aws_file_get_length(impl->file, length);
284 }
285
s_aws_input_stream_file_destroy(struct aws_input_stream * stream)286 static void s_aws_input_stream_file_destroy(struct aws_input_stream *stream) {
287 struct aws_input_stream_file_impl *impl = stream->impl;
288
289 if (impl->close_on_clean_up && impl->file) {
290 fclose(impl->file);
291 }
292
293 aws_mem_release(stream->allocator, stream);
294 }
295
296 static struct aws_input_stream_vtable s_aws_input_stream_file_vtable = {
297 .seek = s_aws_input_stream_file_seek,
298 .read = s_aws_input_stream_file_read,
299 .get_status = s_aws_input_stream_file_get_status,
300 .get_length = s_aws_input_stream_file_get_length,
301 .destroy = s_aws_input_stream_file_destroy};
302
aws_input_stream_new_from_file(struct aws_allocator * allocator,const char * file_name)303 struct aws_input_stream *aws_input_stream_new_from_file(struct aws_allocator *allocator, const char *file_name) {
304
305 struct aws_input_stream *input_stream = NULL;
306 struct aws_input_stream_file_impl *impl = NULL;
307
308 aws_mem_acquire_many(
309 allocator, 2, &input_stream, sizeof(struct aws_input_stream), &impl, sizeof(struct aws_input_stream_file_impl));
310
311 if (!input_stream) {
312 return NULL;
313 }
314
315 AWS_ZERO_STRUCT(*input_stream);
316 AWS_ZERO_STRUCT(*impl);
317
318 input_stream->allocator = allocator;
319 input_stream->vtable = &s_aws_input_stream_file_vtable;
320 input_stream->impl = impl;
321
322 impl->file = aws_fopen(file_name, "r+b");
323 if (impl->file == NULL) {
324 aws_translate_and_raise_io_error(errno);
325 goto on_error;
326 }
327
328 impl->close_on_clean_up = true;
329
330 return input_stream;
331
332 on_error:
333
334 aws_input_stream_destroy(input_stream);
335
336 return NULL;
337 }
338
aws_input_stream_new_from_open_file(struct aws_allocator * allocator,FILE * file)339 struct aws_input_stream *aws_input_stream_new_from_open_file(struct aws_allocator *allocator, FILE *file) {
340 struct aws_input_stream *input_stream = NULL;
341 struct aws_input_stream_file_impl *impl = NULL;
342
343 aws_mem_acquire_many(
344 allocator, 2, &input_stream, sizeof(struct aws_input_stream), &impl, sizeof(struct aws_input_stream_file_impl));
345
346 if (!input_stream) {
347 return NULL;
348 }
349
350 AWS_ZERO_STRUCT(*input_stream);
351 AWS_ZERO_STRUCT(*impl);
352
353 input_stream->allocator = allocator;
354 input_stream->vtable = &s_aws_input_stream_file_vtable;
355 input_stream->impl = impl;
356
357 impl->file = file;
358 impl->close_on_clean_up = false;
359
360 return input_stream;
361 }
362