1 /*
2  * This file is part of the MicroPython project, http://micropython.org/
3  *
4  * The MIT License (MIT)
5  *
6  * Copyright (c) 2014 Damien P. George
7  * Copyright (c) 2014-2016 Paul Sokolovsky
8  *
9  * Permission is hereby granted, free of charge, to any person obtaining a copy
10  * of this software and associated documentation files (the "Software"), to deal
11  * in the Software without restriction, including without limitation the rights
12  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13  * copies of the Software, and to permit persons to whom the Software is
14  * furnished to do so, subject to the following conditions:
15  *
16  * The above copyright notice and this permission notice shall be included in
17  * all copies or substantial portions of the Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25  * THE SOFTWARE.
26  */
27 
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include "py/objstr.h"
32 #include "py/stream.h"
33 #include "py/runtime.h"
34 
35 // This file defines generic Python stream read/write methods which
36 // dispatch to the underlying stream interface of an object.
37 
38 // TODO: should be in mpconfig.h
39 #define DEFAULT_BUFFER_SIZE 256
40 
41 STATIC mp_obj_t stream_readall(mp_obj_t self_in);
42 
43 #define STREAM_CONTENT_TYPE(stream) (((stream)->is_text) ? &mp_type_str : &mp_type_bytes)
44 
45 // Returns error condition in *errcode, if non-zero, return value is number of bytes written
46 // before error condition occurred. If *errcode == 0, returns total bytes written (which will
47 // be equal to input size).
mp_stream_rw(mp_obj_t stream,void * buf_,mp_uint_t size,int * errcode,byte flags)48 mp_uint_t mp_stream_rw(mp_obj_t stream, void *buf_, mp_uint_t size, int *errcode, byte flags) {
49     byte *buf = buf_;
50     typedef mp_uint_t (*io_func_t)(mp_obj_t obj, void *buf, mp_uint_t size, int *errcode);
51     io_func_t io_func;
52     const mp_stream_p_t *stream_p = mp_get_stream(stream);
53     if (flags & MP_STREAM_RW_WRITE) {
54         io_func = (io_func_t)stream_p->write;
55     } else {
56         io_func = stream_p->read;
57     }
58 
59     *errcode = 0;
60     mp_uint_t done = 0;
61     while (size > 0) {
62         mp_uint_t out_sz = io_func(stream, buf, size, errcode);
63         // For read, out_sz == 0 means EOF. For write, it's unspecified
64         // what it means, but we don't make any progress, so returning
65         // is still the best option.
66         if (out_sz == 0) {
67             return done;
68         }
69         if (out_sz == MP_STREAM_ERROR) {
70             // If we read something before getting EAGAIN, don't leak it
71             if (mp_is_nonblocking_error(*errcode) && done != 0) {
72                 *errcode = 0;
73             }
74             return done;
75         }
76         if (flags & MP_STREAM_RW_ONCE) {
77             return out_sz;
78         }
79 
80         buf += out_sz;
81         size -= out_sz;
82         done += out_sz;
83     }
84     return done;
85 }
86 
mp_get_stream_raise(mp_obj_t self_in,int flags)87 const mp_stream_p_t *mp_get_stream_raise(mp_obj_t self_in, int flags) {
88     const mp_obj_type_t *type = mp_obj_get_type(self_in);
89     const mp_stream_p_t *stream_p = type->protocol;
90     if (stream_p == NULL
91         || ((flags & MP_STREAM_OP_READ) && stream_p->read == NULL)
92         || ((flags & MP_STREAM_OP_WRITE) && stream_p->write == NULL)
93         || ((flags & MP_STREAM_OP_IOCTL) && stream_p->ioctl == NULL)) {
94         // CPython: io.UnsupportedOperation, OSError subclass
95         mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("stream operation not supported"));
96     }
97     return stream_p;
98 }
99 
stream_read_generic(size_t n_args,const mp_obj_t * args,byte flags)100 STATIC mp_obj_t stream_read_generic(size_t n_args, const mp_obj_t *args, byte flags) {
101     // What to do if sz < -1?  Python docs don't specify this case.
102     // CPython does a readall, but here we silently let negatives through,
103     // and they will cause a MemoryError.
104     mp_int_t sz;
105     if (n_args == 1 || ((sz = mp_obj_get_int(args[1])) == -1)) {
106         return stream_readall(args[0]);
107     }
108 
109     const mp_stream_p_t *stream_p = mp_get_stream(args[0]);
110 
111     #if MICROPY_PY_BUILTINS_STR_UNICODE
112     if (stream_p->is_text) {
113         // We need to read sz number of unicode characters.  Because we don't have any
114         // buffering, and because the stream API can only read bytes, we must read here
115         // in units of bytes and must never over read.  If we want sz chars, then reading
116         // sz bytes will never over-read, so we follow this approach, in a loop to keep
117         // reading until we have exactly enough chars.  This will be 1 read for text
118         // with ASCII-only chars, and about 2 reads for text with a couple of non-ASCII
119         // chars.  For text with lots of non-ASCII chars, it'll be pretty inefficient
120         // in time and memory.
121 
122         vstr_t vstr;
123         vstr_init(&vstr, sz);
124         mp_uint_t more_bytes = sz;
125         mp_uint_t last_buf_offset = 0;
126         while (more_bytes > 0) {
127             char *p = vstr_add_len(&vstr, more_bytes);
128             int error;
129             mp_uint_t out_sz = mp_stream_read_exactly(args[0], p, more_bytes, &error);
130             if (error != 0) {
131                 vstr_cut_tail_bytes(&vstr, more_bytes);
132                 if (mp_is_nonblocking_error(error)) {
133                     // With non-blocking streams, we read as much as we can.
134                     // If we read nothing, return None, just like read().
135                     // Otherwise, return data read so far.
136                     // TODO what if we have read only half a non-ASCII char?
137                     if (vstr.len == 0) {
138                         vstr_clear(&vstr);
139                         return mp_const_none;
140                     }
141                     break;
142                 }
143                 mp_raise_OSError(error);
144             }
145 
146             if (out_sz < more_bytes) {
147                 // Finish reading.
148                 // TODO what if we have read only half a non-ASCII char?
149                 vstr_cut_tail_bytes(&vstr, more_bytes - out_sz);
150                 if (out_sz == 0) {
151                     break;
152                 }
153             }
154 
155             // count chars from bytes just read
156             for (mp_uint_t off = last_buf_offset;;) {
157                 byte b = vstr.buf[off];
158                 int n;
159                 if (!UTF8_IS_NONASCII(b)) {
160                     // 1-byte ASCII char
161                     n = 1;
162                 } else if ((b & 0xe0) == 0xc0) {
163                     // 2-byte char
164                     n = 2;
165                 } else if ((b & 0xf0) == 0xe0) {
166                     // 3-byte char
167                     n = 3;
168                 } else if ((b & 0xf8) == 0xf0) {
169                     // 4-byte char
170                     n = 4;
171                 } else {
172                     // TODO
173                     n = 5;
174                 }
175                 if (off + n <= vstr.len) {
176                     // got a whole char in n bytes
177                     off += n;
178                     sz -= 1;
179                     last_buf_offset = off;
180                     if (off >= vstr.len) {
181                         more_bytes = sz;
182                         break;
183                     }
184                 } else {
185                     // didn't get a whole char, so work out how many extra bytes are needed for
186                     // this partial char, plus bytes for additional chars that we want
187                     more_bytes = (off + n - vstr.len) + (sz - 1);
188                     break;
189                 }
190             }
191         }
192 
193         return mp_obj_new_str_from_vstr(&mp_type_str, &vstr);
194     }
195     #endif
196 
197     vstr_t vstr;
198     vstr_init_len(&vstr, sz);
199     int error;
200     mp_uint_t out_sz = mp_stream_rw(args[0], vstr.buf, sz, &error, flags);
201     if (error != 0) {
202         vstr_clear(&vstr);
203         if (mp_is_nonblocking_error(error)) {
204             // https://docs.python.org/3.4/library/io.html#io.RawIOBase.read
205             // "If the object is in non-blocking mode and no bytes are available,
206             // None is returned."
207             // This is actually very weird, as naive truth check will treat
208             // this as EOF.
209             return mp_const_none;
210         }
211         mp_raise_OSError(error);
212     } else {
213         vstr.len = out_sz;
214         return mp_obj_new_str_from_vstr(STREAM_CONTENT_TYPE(stream_p), &vstr);
215     }
216 }
217 
stream_read(size_t n_args,const mp_obj_t * args)218 STATIC mp_obj_t stream_read(size_t n_args, const mp_obj_t *args) {
219     return stream_read_generic(n_args, args, MP_STREAM_RW_READ);
220 }
221 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_read_obj, 1, 2, stream_read);
222 
stream_read1(size_t n_args,const mp_obj_t * args)223 STATIC mp_obj_t stream_read1(size_t n_args, const mp_obj_t *args) {
224     return stream_read_generic(n_args, args, MP_STREAM_RW_READ | MP_STREAM_RW_ONCE);
225 }
226 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_read1_obj, 1, 2, stream_read1);
227 
mp_stream_write(mp_obj_t self_in,const void * buf,size_t len,byte flags)228 mp_obj_t mp_stream_write(mp_obj_t self_in, const void *buf, size_t len, byte flags) {
229     int error;
230     mp_uint_t out_sz = mp_stream_rw(self_in, (void *)buf, len, &error, flags);
231     if (error != 0) {
232         if (mp_is_nonblocking_error(error)) {
233             // http://docs.python.org/3/library/io.html#io.RawIOBase.write
234             // "None is returned if the raw stream is set not to block and
235             // no single byte could be readily written to it."
236             return mp_const_none;
237         }
238         mp_raise_OSError(error);
239     } else {
240         return MP_OBJ_NEW_SMALL_INT(out_sz);
241     }
242 }
243 
244 // This is used to adapt a stream object to an mp_print_t interface
mp_stream_write_adaptor(void * self,const char * buf,size_t len)245 void mp_stream_write_adaptor(void *self, const char *buf, size_t len) {
246     mp_stream_write(MP_OBJ_FROM_PTR(self), buf, len, MP_STREAM_RW_WRITE);
247 }
248 
stream_write_method(size_t n_args,const mp_obj_t * args)249 STATIC mp_obj_t stream_write_method(size_t n_args, const mp_obj_t *args) {
250     mp_buffer_info_t bufinfo;
251     mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ);
252     size_t max_len = (size_t)-1;
253     size_t off = 0;
254     if (n_args == 3) {
255         max_len = mp_obj_get_int_truncated(args[2]);
256     } else if (n_args == 4) {
257         off = mp_obj_get_int_truncated(args[2]);
258         max_len = mp_obj_get_int_truncated(args[3]);
259         if (off > bufinfo.len) {
260             off = bufinfo.len;
261         }
262     }
263     bufinfo.len -= off;
264     return mp_stream_write(args[0], (byte *)bufinfo.buf + off, MIN(bufinfo.len, max_len), MP_STREAM_RW_WRITE);
265 }
266 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_write_obj, 2, 4, stream_write_method);
267 
stream_write1_method(mp_obj_t self_in,mp_obj_t arg)268 STATIC mp_obj_t stream_write1_method(mp_obj_t self_in, mp_obj_t arg) {
269     mp_buffer_info_t bufinfo;
270     mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ);
271     return mp_stream_write(self_in, bufinfo.buf, bufinfo.len, MP_STREAM_RW_WRITE | MP_STREAM_RW_ONCE);
272 }
273 MP_DEFINE_CONST_FUN_OBJ_2(mp_stream_write1_obj, stream_write1_method);
274 
stream_readinto(size_t n_args,const mp_obj_t * args)275 STATIC mp_obj_t stream_readinto(size_t n_args, const mp_obj_t *args) {
276     mp_buffer_info_t bufinfo;
277     mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_WRITE);
278 
279     // CPython extension: if 2nd arg is provided, that's max len to read,
280     // instead of full buffer. Similar to
281     // https://docs.python.org/3/library/socket.html#socket.socket.recv_into
282     mp_uint_t len = bufinfo.len;
283     if (n_args > 2) {
284         len = mp_obj_get_int(args[2]);
285         if (len > bufinfo.len) {
286             len = bufinfo.len;
287         }
288     }
289 
290     int error;
291     mp_uint_t out_sz = mp_stream_read_exactly(args[0], bufinfo.buf, len, &error);
292     if (error != 0) {
293         if (mp_is_nonblocking_error(error)) {
294             return mp_const_none;
295         }
296         mp_raise_OSError(error);
297     } else {
298         return MP_OBJ_NEW_SMALL_INT(out_sz);
299     }
300 }
301 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_readinto_obj, 2, 3, stream_readinto);
302 
stream_readall(mp_obj_t self_in)303 STATIC mp_obj_t stream_readall(mp_obj_t self_in) {
304     const mp_stream_p_t *stream_p = mp_get_stream(self_in);
305 
306     mp_uint_t total_size = 0;
307     vstr_t vstr;
308     vstr_init(&vstr, DEFAULT_BUFFER_SIZE);
309     char *p = vstr.buf;
310     mp_uint_t current_read = DEFAULT_BUFFER_SIZE;
311     while (true) {
312         int error;
313         mp_uint_t out_sz = stream_p->read(self_in, p, current_read, &error);
314         if (out_sz == MP_STREAM_ERROR) {
315             if (mp_is_nonblocking_error(error)) {
316                 // With non-blocking streams, we read as much as we can.
317                 // If we read nothing, return None, just like read().
318                 // Otherwise, return data read so far.
319                 if (total_size == 0) {
320                     return mp_const_none;
321                 }
322                 break;
323             }
324             mp_raise_OSError(error);
325         }
326         if (out_sz == 0) {
327             break;
328         }
329         total_size += out_sz;
330         if (out_sz < current_read) {
331             current_read -= out_sz;
332             p += out_sz;
333         } else {
334             p = vstr_extend(&vstr, DEFAULT_BUFFER_SIZE);
335             current_read = DEFAULT_BUFFER_SIZE;
336         }
337     }
338 
339     vstr.len = total_size;
340     return mp_obj_new_str_from_vstr(STREAM_CONTENT_TYPE(stream_p), &vstr);
341 }
342 
343 // Unbuffered, inefficient implementation of readline() for raw I/O files.
stream_unbuffered_readline(size_t n_args,const mp_obj_t * args)344 STATIC mp_obj_t stream_unbuffered_readline(size_t n_args, const mp_obj_t *args) {
345     const mp_stream_p_t *stream_p = mp_get_stream(args[0]);
346 
347     mp_int_t max_size = -1;
348     if (n_args > 1) {
349         max_size = MP_OBJ_SMALL_INT_VALUE(args[1]);
350     }
351 
352     vstr_t vstr;
353     if (max_size != -1) {
354         vstr_init(&vstr, max_size);
355     } else {
356         vstr_init(&vstr, 16);
357     }
358 
359     while (max_size == -1 || max_size-- != 0) {
360         char *p = vstr_add_len(&vstr, 1);
361         int error;
362         mp_uint_t out_sz = stream_p->read(args[0], p, 1, &error);
363         if (out_sz == MP_STREAM_ERROR) {
364             if (mp_is_nonblocking_error(error)) {
365                 if (vstr.len == 1) {
366                     // We just incremented it, but otherwise we read nothing
367                     // and immediately got EAGAIN. This case is not well
368                     // specified in
369                     // https://docs.python.org/3/library/io.html#io.IOBase.readline
370                     // unlike similar case for read(). But we follow the latter's
371                     // behavior - return None.
372                     vstr_clear(&vstr);
373                     return mp_const_none;
374                 } else {
375                     goto done;
376                 }
377             }
378             mp_raise_OSError(error);
379         }
380         if (out_sz == 0) {
381         done:
382             // Back out previously added byte
383             // Consider, what's better - read a char and get OutOfMemory (so read
384             // char is lost), or allocate first as we do.
385             vstr_cut_tail_bytes(&vstr, 1);
386             break;
387         }
388         if (*p == '\n') {
389             break;
390         }
391     }
392 
393     return mp_obj_new_str_from_vstr(STREAM_CONTENT_TYPE(stream_p), &vstr);
394 }
395 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_unbuffered_readline_obj, 1, 2, stream_unbuffered_readline);
396 
397 // TODO take an optional extra argument (what does it do exactly?)
stream_unbuffered_readlines(mp_obj_t self)398 STATIC mp_obj_t stream_unbuffered_readlines(mp_obj_t self) {
399     mp_obj_t lines = mp_obj_new_list(0, NULL);
400     for (;;) {
401         mp_obj_t line = stream_unbuffered_readline(1, &self);
402         if (!mp_obj_is_true(line)) {
403             break;
404         }
405         mp_obj_list_append(lines, line);
406     }
407     return lines;
408 }
409 MP_DEFINE_CONST_FUN_OBJ_1(mp_stream_unbuffered_readlines_obj, stream_unbuffered_readlines);
410 
mp_stream_unbuffered_iter(mp_obj_t self)411 mp_obj_t mp_stream_unbuffered_iter(mp_obj_t self) {
412     mp_obj_t l_in = stream_unbuffered_readline(1, &self);
413     if (mp_obj_is_true(l_in)) {
414         return l_in;
415     }
416     return MP_OBJ_STOP_ITERATION;
417 }
418 
mp_stream_close(mp_obj_t stream)419 mp_obj_t mp_stream_close(mp_obj_t stream) {
420     const mp_stream_p_t *stream_p = mp_get_stream(stream);
421     int error;
422     mp_uint_t res = stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error);
423     if (res == MP_STREAM_ERROR) {
424         mp_raise_OSError(error);
425     }
426     return mp_const_none;
427 }
428 MP_DEFINE_CONST_FUN_OBJ_1(mp_stream_close_obj, mp_stream_close);
429 
stream_seek(size_t n_args,const mp_obj_t * args)430 STATIC mp_obj_t stream_seek(size_t n_args, const mp_obj_t *args) {
431     struct mp_stream_seek_t seek_s;
432     // TODO: Could be uint64
433     seek_s.offset = mp_obj_get_int(args[1]);
434     seek_s.whence = SEEK_SET;
435     if (n_args == 3) {
436         seek_s.whence = mp_obj_get_int(args[2]);
437     }
438 
439     // In POSIX, it's error to seek before end of stream, we enforce it here.
440     if (seek_s.whence == SEEK_SET && seek_s.offset < 0) {
441         mp_raise_OSError(MP_EINVAL);
442     }
443 
444     const mp_stream_p_t *stream_p = mp_get_stream(args[0]);
445     int error;
446     mp_uint_t res = stream_p->ioctl(args[0], MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error);
447     if (res == MP_STREAM_ERROR) {
448         mp_raise_OSError(error);
449     }
450 
451     // TODO: Could be uint64
452     return mp_obj_new_int_from_uint(seek_s.offset);
453 }
454 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_seek_obj, 2, 3, stream_seek);
455 
stream_tell(mp_obj_t self)456 STATIC mp_obj_t stream_tell(mp_obj_t self) {
457     mp_obj_t offset = MP_OBJ_NEW_SMALL_INT(0);
458     mp_obj_t whence = MP_OBJ_NEW_SMALL_INT(SEEK_CUR);
459     const mp_obj_t args[3] = {self, offset, whence};
460     return stream_seek(3, args);
461 }
462 MP_DEFINE_CONST_FUN_OBJ_1(mp_stream_tell_obj, stream_tell);
463 
stream_flush(mp_obj_t self)464 STATIC mp_obj_t stream_flush(mp_obj_t self) {
465     const mp_stream_p_t *stream_p = mp_get_stream(self);
466     int error;
467     mp_uint_t res = stream_p->ioctl(self, MP_STREAM_FLUSH, 0, &error);
468     if (res == MP_STREAM_ERROR) {
469         mp_raise_OSError(error);
470     }
471     return mp_const_none;
472 }
473 MP_DEFINE_CONST_FUN_OBJ_1(mp_stream_flush_obj, stream_flush);
474 
stream_ioctl(size_t n_args,const mp_obj_t * args)475 STATIC mp_obj_t stream_ioctl(size_t n_args, const mp_obj_t *args) {
476     mp_buffer_info_t bufinfo;
477     uintptr_t val = 0;
478     if (n_args > 2) {
479         if (mp_get_buffer(args[2], &bufinfo, MP_BUFFER_WRITE)) {
480             val = (uintptr_t)bufinfo.buf;
481         } else {
482             val = mp_obj_get_int_truncated(args[2]);
483         }
484     }
485 
486     const mp_stream_p_t *stream_p = mp_get_stream(args[0]);
487     int error;
488     mp_uint_t res = stream_p->ioctl(args[0], mp_obj_get_int(args[1]), val, &error);
489     if (res == MP_STREAM_ERROR) {
490         mp_raise_OSError(error);
491     }
492 
493     return mp_obj_new_int(res);
494 }
495 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_ioctl_obj, 2, 3, stream_ioctl);
496 
497 #if MICROPY_STREAMS_POSIX_API
498 /*
499  * POSIX-like functions
500  *
501  * These functions have POSIX-compatible signature (except for "void *stream"
502  * first argument instead of "int fd"). They are useful to port existing
503  * POSIX-compatible software to work with MicroPython streams.
504  */
505 
506 #include <errno.h>
507 
mp_stream_posix_write(void * stream,const void * buf,size_t len)508 ssize_t mp_stream_posix_write(void *stream, const void *buf, size_t len) {
509     mp_obj_base_t *o = stream;
510     const mp_stream_p_t *stream_p = o->type->protocol;
511     mp_uint_t out_sz = stream_p->write(MP_OBJ_FROM_PTR(stream), buf, len, &errno);
512     if (out_sz == MP_STREAM_ERROR) {
513         return -1;
514     } else {
515         return out_sz;
516     }
517 }
518 
mp_stream_posix_read(void * stream,void * buf,size_t len)519 ssize_t mp_stream_posix_read(void *stream, void *buf, size_t len) {
520     mp_obj_base_t *o = stream;
521     const mp_stream_p_t *stream_p = o->type->protocol;
522     mp_uint_t out_sz = stream_p->read(MP_OBJ_FROM_PTR(stream), buf, len, &errno);
523     if (out_sz == MP_STREAM_ERROR) {
524         return -1;
525     } else {
526         return out_sz;
527     }
528 }
529 
mp_stream_posix_lseek(void * stream,off_t offset,int whence)530 off_t mp_stream_posix_lseek(void *stream, off_t offset, int whence) {
531     const mp_obj_base_t *o = stream;
532     const mp_stream_p_t *stream_p = o->type->protocol;
533     struct mp_stream_seek_t seek_s;
534     seek_s.offset = offset;
535     seek_s.whence = whence;
536     mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &errno);
537     if (res == MP_STREAM_ERROR) {
538         return -1;
539     }
540     return seek_s.offset;
541 }
542 
mp_stream_posix_fsync(void * stream)543 int mp_stream_posix_fsync(void *stream) {
544     mp_obj_base_t *o = stream;
545     const mp_stream_p_t *stream_p = o->type->protocol;
546     mp_uint_t res = stream_p->ioctl(MP_OBJ_FROM_PTR(stream), MP_STREAM_FLUSH, 0, &errno);
547     if (res == MP_STREAM_ERROR) {
548         return -1;
549     }
550     return res;
551 }
552 
553 #endif
554