diff --git a/py/qstrdefs.h b/py/qstrdefs.h
index 6960bd06cbb70dea8d4c35d832dc50b7da964eda..bcaed02d8b4ced7e4b5d8d809ec1b832b83ec1bc 100644
--- a/py/qstrdefs.h
+++ b/py/qstrdefs.h
@@ -314,6 +314,7 @@ Q(unpack)
 Q(io)
 Q(readall)
 Q(readline)
+Q(readlines)
 Q(StringIO)
 Q(BytesIO)
 Q(getvalue)
diff --git a/py/stream.c b/py/stream.c
index 113eaa46ee0a9a019cae488e6c1e47332b3fd952..f20ee0f5e81325c5f37a463a28dec8886a73a4c4 100644
--- a/py/stream.c
+++ b/py/stream.c
@@ -123,8 +123,7 @@ STATIC mp_obj_t stream_unbuffered_readline(uint n_args, const mp_obj_t *args) {
     while (max_size == -1 || max_size-- != 0) {
         char *p = vstr_add_len(vstr, 1);
         if (p == NULL) {
-            // TODO
-            nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_OSError/*&mp_type_RuntimeError*/, "Out of memory"));
+            nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_MemoryError, "out of memory"));
         }
 
         machine_int_t out_sz = o->type->stream_p->read(o, p, 1, &error);
@@ -143,16 +142,29 @@ STATIC mp_obj_t stream_unbuffered_readline(uint n_args, const mp_obj_t *args) {
             break;
         }
     }
-    // TODO don't intern this string
-    vstr_shrink(vstr);
-    return MP_OBJ_NEW_QSTR(qstr_from_strn_take(vstr_str(vstr), vstr->alloc, vstr_len(vstr)));
+    // TODO need a string creation API that doesn't copy the given data
+    mp_obj_t ret = mp_obj_new_str((byte*)vstr->buf, vstr->len, false);
+    vstr_free(vstr);
+    return ret;
+}
+
+// TODO take an optional extra argument (what does it do exactly?)
+STATIC mp_obj_t stream_unbuffered_readlines(mp_obj_t self) {
+    mp_obj_t lines = mp_obj_new_list(0, NULL);
+    for (;;) {
+        mp_obj_t line = stream_unbuffered_readline(1, &self);
+        if (mp_obj_str_get_len(line) == 0) {
+            break;
+        }
+        mp_obj_list_append(lines, line);
+    }
+    return lines;
 }
+MP_DEFINE_CONST_FUN_OBJ_1(mp_stream_unbuffered_readlines_obj, stream_unbuffered_readlines);
 
 mp_obj_t mp_stream_unbuffered_iter(mp_obj_t self) {
     mp_obj_t l_in = stream_unbuffered_readline(1, &self);
-    uint sz;
-    mp_obj_str_get_data(l_in, &sz);
-    if (sz != 0) {
+    if (mp_obj_str_get_len(l_in) != 0) {
         return l_in;
     }
     return MP_OBJ_STOP_ITERATION;
diff --git a/py/stream.h b/py/stream.h
index a0cc34797bcc7a7ebc767aae37b95ad654d7c31f..cb70a3d91434cdb26d9cea764cc14c627f1f57d4 100644
--- a/py/stream.h
+++ b/py/stream.h
@@ -1,7 +1,8 @@
-extern const mp_obj_fun_native_t mp_stream_read_obj;
-extern const mp_obj_fun_native_t mp_stream_readall_obj;
-extern const mp_obj_fun_native_t mp_stream_unbuffered_readline_obj;
-extern const mp_obj_fun_native_t mp_stream_write_obj;
+MP_DECLARE_CONST_FUN_OBJ(mp_stream_read_obj);
+MP_DECLARE_CONST_FUN_OBJ(mp_stream_readall_obj);
+MP_DECLARE_CONST_FUN_OBJ(mp_stream_unbuffered_readline_obj);
+MP_DECLARE_CONST_FUN_OBJ(mp_stream_unbuffered_readlines_obj);
+MP_DECLARE_CONST_FUN_OBJ(mp_stream_write_obj);
 
 // Iterator which uses mp_stream_unbuffered_readline_obj
 mp_obj_t mp_stream_unbuffered_iter(mp_obj_t self);
diff --git a/stmhal/file.c b/stmhal/file.c
index 978ba65fc6caac63adaa6e7e7cea0c513947685c..f78709b9f541b633d23c7acad2ff46236c35ec8a 100644
--- a/stmhal/file.c
+++ b/stmhal/file.c
@@ -51,6 +51,7 @@ STATIC const mp_map_elem_t file_locals_dict_table[] = {
     { MP_OBJ_NEW_QSTR(MP_QSTR_read), (mp_obj_t)&mp_stream_read_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_readall), (mp_obj_t)&mp_stream_readall_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_readline), (mp_obj_t)&mp_stream_unbuffered_readline_obj},
+    { MP_OBJ_NEW_QSTR(MP_QSTR_readlines), (mp_obj_t)&mp_stream_unbuffered_readlines_obj},
     { MP_OBJ_NEW_QSTR(MP_QSTR_write), (mp_obj_t)&mp_stream_write_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&file_obj_close_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR___del__), (mp_obj_t)&file_obj_close_obj },
diff --git a/tests/io/file1.py b/tests/io/file1.py
index 8552f535bd9b0e7de35fcf180ebbfbefcbddcac4..7d5154a4f85ac131d1a66e9a30dfaeb47e6c34cd 100644
--- a/tests/io/file1.py
+++ b/tests/io/file1.py
@@ -2,3 +2,5 @@ f = open("io/data/file1")
 print(f.read(5))
 print(f.readline())
 print(f.read())
+f = open("io/data/file1")
+print(f.readlines())
diff --git a/unix/file.c b/unix/file.c
index 4588d96574ae2ed57c17d2cf4528b5fa315729e3..2b7e1a04f1e45b6fa82938c5f535cae83fab3e42 100644
--- a/unix/file.c
+++ b/unix/file.c
@@ -132,6 +132,7 @@ STATIC const mp_map_elem_t rawfile_locals_dict_table[] = {
     { MP_OBJ_NEW_QSTR(MP_QSTR_read), (mp_obj_t)&mp_stream_read_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_readall), (mp_obj_t)&mp_stream_readall_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_readline), (mp_obj_t)&mp_stream_unbuffered_readline_obj},
+    { MP_OBJ_NEW_QSTR(MP_QSTR_readlines), (mp_obj_t)&mp_stream_unbuffered_readlines_obj},
     { MP_OBJ_NEW_QSTR(MP_QSTR_write), (mp_obj_t)&mp_stream_write_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&fdfile_close_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR___enter__), (mp_obj_t)&mp_identity_obj },