lib/file: revamp `poll_in` to use in `FileReader`
[nit.git] / lib / standard / file.nit
index 3bc2fcd..e5fc85b 100644 (file)
@@ -77,9 +77,10 @@ abstract class FileStream
        # If the buf_size is <= 0, its value will be 512 by default
        #
        # The mode is any of the buffer_mode enumeration in `Sys`:
-       #       - buffer_mode_full
-       #       - buffer_mode_line
-       #       - buffer_mode_none
+       #
+       # * `buffer_mode_full`
+       # * `buffer_mode_line`
+       # * `buffer_mode_none`
        fun set_buffering_mode(buf_size, mode: Int) do
                if buf_size <= 0 then buf_size = 512
                if _file.set_buffering_type(buf_size, mode) != 0 then
@@ -108,7 +109,7 @@ class FileReader
                last_error = null
                _file = new NativeFile.io_open_read(path.to_cstring)
                if _file.address_is_null then
-                       last_error = new IOError("Error: Opening file at '{path.as(not null)}' failed with '{sys.errno.strerror}'")
+                       last_error = new IOError("Cannot open `{path.as(not null)}`: {sys.errno.strerror}")
                        end_reached = true
                        return
                end
@@ -126,6 +127,10 @@ class FileReader
        redef fun fill_buffer
        do
                var nb = _file.io_read(_buffer, _buffer_capacity)
+               if last_error == null and _file.ferror then
+                       last_error = new IOError("Cannot read `{path.as(not null)}`: {sys.errno.strerror}")
+                       end_reached = true
+               end
                if nb <= 0 then
                        end_reached = true
                        nb = 0
@@ -154,7 +159,7 @@ class FileReader
                prepare_buffer(10)
                _file = new NativeFile.io_open_read(path.to_cstring)
                if _file.address_is_null then
-                       last_error = new IOError("Error: Opening file at '{path}' failed with '{sys.errno.strerror}'")
+                       last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
                        end_reached = true
                end
        end
@@ -171,6 +176,20 @@ class FileReader
                        end_reached = true
                end
        end
+
+       redef fun poll_in
+       do
+               var res = native_poll_in(fd)
+               if res == -1 then
+                       last_error = new IOError(errno.to_s)
+                       return false
+               else return res > 0
+       end
+
+       private fun native_poll_in(fd: Int): Int `{
+               struct pollfd fds = {fd, POLLIN, 0};
+               return poll(&fds, 1, 0);
+       `}
 end
 
 # `Stream` that can write to a File
@@ -184,7 +203,7 @@ class FileWriter
                        last_error = new IOError("cannot write to non-writable stream")
                        return
                end
-               write_native(s.items, s.length)
+               write_native(s.items, 0, s.length)
        end
 
        redef fun write(s)
@@ -194,7 +213,7 @@ class FileWriter
                        last_error = new IOError("cannot write to non-writable stream")
                        return
                end
-               for i in s.substrings do write_native(i.to_cstring, i.length)
+               s.write_native_to(self)
        end
 
        redef fun write_byte(value)
@@ -225,7 +244,7 @@ class FileWriter
        redef var is_writable = false
 
        # Write `len` bytes from `native`.
-       private fun write_native(native: NativeString, len: Int)
+       private fun write_native(native: NativeString, from, len: Int)
        do
                if last_error != null then return
                if not _is_writable then
@@ -237,7 +256,7 @@ class FileWriter
                        _is_writable = false
                        return
                end
-               var err = _file.io_write(native, len)
+               var err = _file.io_write(native, from, len)
                if err != len then
                        # Big problem
                        last_error = new IOError("Problem in writing : {err} {len} \n")
@@ -251,7 +270,7 @@ class FileWriter
                self.path = path
                _is_writable = true
                if _file.address_is_null then
-                       last_error = new IOError("Error: Opening file at '{path}' failed with '{sys.errno.strerror}'")
+                       last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
                        is_writable = false
                end
        end
@@ -272,7 +291,9 @@ redef class Int
        # Creates a file stream from a file descriptor `fd` using the file access `mode`.
        #
        # NOTE: The `mode` specified must be compatible with the one used in the file descriptor.
-       private fun fd_to_stream(mode: NativeString): NativeFile is extern "file_int_fdtostream"
+       private fun fd_to_stream(mode: NativeString): NativeFile `{
+               return fdopen(self, mode);
+       `}
 end
 
 # Constant for read-only file streams
@@ -298,8 +319,6 @@ class Stdin
                path = "/dev/stdin"
                prepare_buffer(1)
        end
-
-       redef fun poll_in is extern "file_stdin_poll_in"
 end
 
 # Standard output stream.
@@ -339,9 +358,13 @@ redef class Writable
        end
 end
 
-# Utility class to access file system services
+# Utility class to access file system services.
 #
 # Usually created with `Text::to_path`.
+#
+# `Path` objects does not necessarily represent existing files in a file system.
+# They are sate-less objects that efficiently represent path information.
+# They also provide an easy to use API on file-system services and are used to store their error status (see `last_error`)
 class Path
 
        private var path: String
@@ -349,20 +372,69 @@ class Path
        # Path to this file
        redef fun to_s do return path
 
-       # Name of the file name at `to_s`
+       # Short name of the file at `to_s`
        #
        # ~~~
        # var path = "/tmp/somefile".to_path
        # assert path.filename == "somefile"
        # ~~~
-       var filename: String = path.basename("") is lazy
+       #
+       # The result does not depend of the file system, thus is cached for efficiency.
+       var filename: String = path.basename is lazy
+
+       # The path simplified by removing useless `.`, removing `//`, and resolving `..`
+       #
+       # ~~~
+       # var path = "somedir/./tmp/../somefile".to_path
+       # assert path.simplified.to_s == "somedir/somefile"
+       # ~~~
+       #
+       # See `String:simplify_path` for details.
+       #
+       # The result does not depend of the file system, thus is cached for efficiency.
+       var simplified: Path is lazy do
+               var res = path.simplify_path.to_path
+               res.simplified = res
+               return res
+       end
+
+       # Return the directory part of the path.
+       #
+       # ~~~
+       # var path = "/foo/bar/baz".to_path
+       # assert path.dir.to_s == "/foo/bar"
+       # assert path.dir.dir.to_s == "/foo"
+       # assert path.dir.dir.dir.to_s == "/"
+       # ~~~
+       #
+       # See `String:dirname` for details.
+       #
+       # The result does not depend of the file system, thus is cached for efficiency.
+       var dir: Path is lazy do
+               return path.dirname.to_path
+       end
+
+       # Last error produced by I/O operations.
+       #
+       # ~~~
+       # var path = "/does/not/exists".to_path
+       # assert path.last_error == null
+       # path.read_all
+       # assert path.last_error != null
+       # ~~~
+       #
+       # Since `Path` objects are stateless, `last_error` is reset on most operations and reflect its status.
+       var last_error: nullable IOError = null is writable
 
        # Does the file at `path` exists?
+       #
+       # If the file does not exists, `last_error` is set to the information.
        fun exists: Bool do return stat != null
 
        # Information on the file at `self` following symbolic links
        #
        # Returns `null` if there is no file at `self`.
+       # `last_error` is updated to contains the error information on error, and null on success.
        #
        #     assert "/etc/".to_path.stat.is_dir
        #     assert "/etc/issue".to_path.stat.is_file
@@ -374,12 +446,18 @@ class Path
        # if stat != null then # Does `p` exist?
        #     print "It's size is {stat.size}"
        #     if stat.is_dir then print "It's a directory"
+       # else
+       #     print p.last_error.to_s
        # end
        # ~~~
        fun stat: nullable FileStat
        do
                var stat = path.to_cstring.file_stat
-               if stat.address_is_null then return null
+               if stat.address_is_null then
+                       last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
+                       return null
+               end
+               last_error = null
                return new FileStat(stat)
        end
 
@@ -389,18 +467,33 @@ class Path
        fun link_stat: nullable FileStat
        do
                var stat = path.to_cstring.file_lstat
-               if stat.address_is_null then return null
+               if stat.address_is_null then
+                       last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
+                       return null
+               end
+               last_error = null
                return new FileStat(stat)
        end
 
-       # Delete a file from the file system, return `true` on success
-       fun delete: Bool do return path.to_cstring.file_delete
+       # Delete a file from the file system.
+       #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       fun delete
+       do
+               var res = path.to_cstring.file_delete
+               if not res then
+                       last_error = new IOError("Cannot delete `{path}`: {sys.errno.strerror}")
+               else
+                       last_error = null
+               end
+       end
 
-       # Copy content of file at `path` to `dest`
+       # Copy content of file at `path` to `dest`.
        #
-       # Require: `exists`
+       # `last_error` is updated to contains the error information on error, and null on success.
        fun copy(dest: Path)
        do
+               last_error = null
                var input = open_ro
                var output = dest.open_wo
 
@@ -411,41 +504,75 @@ class Path
 
                input.close
                output.close
+               last_error = input.last_error or else output.last_error
        end
 
-       # Open this file for reading
+       # Open this file for reading.
+       #
+       # ~~~
+       # var file = "/etc/issue".to_path.open_ro
+       # print file.read_line
+       # file.close
+       # ~~~
+       #
+       # Note that it is the user's responsibility to close the stream.
+       # Therefore, for simple use case, look at `read_all` or `each_line`.
        #
-       # Require: `exists and not link_stat.is_dir`
+       # ENSURE `last_error == result.last_error`
        fun open_ro: FileReader
        do
-               # TODO manage streams error when they are merged
-               return new FileReader.open(path)
+               var res = new FileReader.open(path)
+               last_error = res.last_error
+               return res
        end
 
        # Open this file for writing
        #
-       # Require: `not exists or not stat.is_dir`
+       # ~~~
+       # var file = "bla.log".to_path.open_wo
+       # file.write "Blabla\n"
+       # file.close
+       # ~~~
+       #
+       # Note that it is the user's responsibility to close the stream.
+       # Therefore, for simple use case, look at `Writable::write_to_file`.
+       #
+       # ENSURE `last_error == result.last_error`
        fun open_wo: FileWriter
        do
-               # TODO manage streams error when they are merged
-               return new FileWriter.open(path)
+               var res = new FileWriter.open(path)
+               last_error = res.last_error
+               return res
        end
 
-       # Read all the content of the file
+       # Read all the content of the file as a string.
        #
        # ~~~
        # var content = "/etc/issue".to_path.read_all
        # print content
        # ~~~
        #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       # In case of error, the result might be empty or truncated.
+       #
        # See `Reader::read_all` for details.
        fun read_all: String do return read_all_bytes.to_s
 
+       # Read all the content on the file as a raw sequence of bytes.
+       #
+       # ~~~
+       # var content = "/etc/issue".to_path.read_all_bytes
+       # print content.to_s
+       # ~~~
+       #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       # In case of error, the result might be empty or truncated.
        fun read_all_bytes: Bytes
        do
                var s = open_ro
                var res = s.read_all_bytes
                s.close
+               last_error = s.last_error
                return res
        end
 
@@ -462,12 +589,16 @@ class Path
        # end
        # ~~~
        #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       # In case of error, the result might be empty or truncated.
+       #
        # See `Reader::read_lines` for details.
        fun read_lines: Array[String]
        do
                var s = open_ro
                var res = s.read_lines
                s.close
+               last_error = s.last_error
                return res
        end
 
@@ -482,52 +613,97 @@ class Path
        #
        # Note: the stream is automatically closed at the end of the file (see `LineIterator::close_on_finish`)
        #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       #
        # See `Reader::each_line` for details.
        fun each_line: LineIterator
        do
                var s = open_ro
                var res = s.each_line
                res.close_on_finish = true
+               last_error = s.last_error
                return res
        end
 
 
-       # Lists the name of the files contained within the directory at `path`
+       # Lists the files contained within the directory at `path`.
        #
-       # Require: `exists and is_dir`
+       #     var files = "/etc".to_path.files
+       #     assert files.has("/etc/issue".to_path)
+       #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       # In case of error, the result might be empty or truncated.
+       #
+       #     var path = "/etc/issue".to_path
+       #     files = path.files
+       #     assert files.is_empty
+       #     assert path.last_error != null
        fun files: Array[Path]
        do
-               var files = new Array[Path]
-               for filename in path.files do
-                       files.add new Path(path / filename)
+               last_error = null
+               var res = new Array[Path]
+               var d = new NativeDir.opendir(path.to_cstring)
+               if d.address_is_null then
+                       last_error = new IOError("Cannot list directory `{path}`: {sys.errno.strerror}")
+                       return res
+               end
+
+               loop
+                       var de = d.readdir
+                       if de.address_is_null then
+                               # readdir cannot fail, so null means end of list
+                               break
+                       end
+                       var name = de.to_s_with_copy
+                       if name == "." or name == ".." then continue
+                       res.add new Path(path / name)
                end
-               return files
+               d.closedir
+
+               return res
        end
 
-       # Delete a directory and all of its content, return `true` on success
+       # Delete a directory and all of its content
        #
        # Does not go through symbolic links and may get stuck in a cycle if there
        # is a cycle in the file system.
-       fun rmdir: Bool
+       #
+       # `last_error` is updated to contains the error information on error, and null on success.
+       # The method does not stop on the first error and try to remove most file and directories.
+       #
+       # ~~~
+       # var path = "/does/not/exists/".to_path
+       # path.rmdir
+       # assert path.last_error != null
+       # ~~~
+       fun rmdir
        do
-               var ok = true
+               last_error = null
                for file in self.files do
                        var stat = file.link_stat
+                       if stat == null then
+                               last_error = file.last_error
+                               continue
+                       end
                        if stat.is_dir then
-                               ok = file.rmdir and ok
+                               # Recursively rmdir
+                               file.rmdir
                        else
-                               ok = file.delete and ok
+                               file.delete
                        end
+                       if last_error == null then last_error = file.last_error
                end
 
-               # Delete the directory itself
-               if ok then ok = path.to_cstring.rmdir and ok
-
-               return ok
+               # Delete the directory itself if things are fine
+               if last_error == null then
+                       if path.to_cstring.rmdir then
+                               last_error = new IOError("Cannot remove `{self}`: {sys.errno.strerror}")
+                       end
+               end
        end
 
-       redef fun ==(other) do return other isa Path and path.simplify_path == other.path.simplify_path
-       redef fun hash do return path.simplify_path.hash
+       redef fun ==(other) do return other isa Path and simplified.path == other.simplified.path
+       redef fun hash do return simplified.path.hash
 end
 
 # Information on a file
@@ -665,6 +841,11 @@ end
 redef class Text
        # Access file system related services on the path at `self`
        fun to_path: Path do return new Path(to_s)
+
+       private fun write_native_to(s: FileWriter)
+       do
+               for i in substrings do s.write_native(i.to_cstring, 0, i.bytelen)
+       end
 end
 
 redef class String
@@ -693,36 +874,51 @@ redef class String
        # Copy content of file at `self` to `dest`
        fun file_copy_to(dest: String) do to_path.copy(dest.to_path)
 
-       # Remove the trailing extension `ext`.
+       # Remove the trailing `extension`.
+       #
+       # `extension` usually starts with a dot but could be anything.
        #
-       # `ext` usually starts with a dot but could be anything.
+       #     assert "file.txt".strip_extension(".txt")   == "file"
+       #     assert "file.txt".strip_extension("le.txt") == "fi"
+       #     assert "file.txt".strip_extension("xt")     == "file.t"
        #
-       #     assert "file.txt".strip_extension(".txt")  == "file"
-       #     assert "file.txt".strip_extension("le.txt")  == "fi"
-       #     assert "file.txt".strip_extension("xt")  == "file.t"
+       # If `extension == null`, the rightmost extension is stripped, including the last dot.
        #
-       # if `ext` is not present, `self` is returned unmodified.
+       #     assert "file.txt".strip_extension           == "file"
+       #
+       # If `extension` is not present, `self` is returned unmodified.
        #
        #     assert "file.txt".strip_extension(".tar.gz")  == "file.txt"
-       fun strip_extension(ext: String): String
+       fun strip_extension(extension: nullable String): String
        do
-               if has_suffix(ext) then
-                       return substring(0, length - ext.length)
+               if extension == null then
+                       extension = file_extension
+                       if extension == null then
+                               return self
+                       else extension = ".{extension}"
+               end
+
+               if has_suffix(extension) then
+                       return substring(0, length - extension.length)
                end
                return self
        end
 
-       # Extract the basename of a path and remove the extension
+       # Extract the basename of a path and strip the `extension`
        #
-       #     assert "/path/to/a_file.ext".basename(".ext")         == "a_file"
-       #     assert "path/to/a_file.ext".basename(".ext")          == "a_file"
-       #     assert "path/to".basename(".ext")                     == "to"
-       #     assert "path/to/".basename(".ext")                    == "to"
+       # The extension is stripped only if `extension != null`.
+       #
+       #     assert "/path/to/a_file.ext".basename(".ext")     == "a_file"
+       #     assert "path/to/a_file.ext".basename(".ext")      == "a_file"
+       #     assert "path/to/a_file.ext".basename              == "a_file.ext"
+       #     assert "path/to".basename(".ext")                 == "to"
+       #     assert "path/to/".basename(".ext")                == "to"
+       #     assert "path/to".basename                         == "to"
        #     assert "path".basename("")                        == "path"
        #     assert "/path".basename("")                       == "path"
        #     assert "/".basename("")                           == "/"
        #     assert "".basename("")                            == ""
-       fun basename(ext: String): String
+       fun basename(extension: nullable String): String
        do
                var l = length - 1 # Index of the last char
                while l > 0 and self.chars[l] == '/' do l -= 1 # remove all trailing `/`
@@ -732,7 +928,10 @@ redef class String
                if pos >= 0 then
                        n = substring(pos+1, l-pos)
                end
-               return n.strip_extension(ext)
+
+               if extension != null then
+                       return n.strip_extension(extension)
+               else return n
        end
 
        # Extract the dirname of a path
@@ -891,7 +1090,7 @@ redef class String
        # In other cases, parts of the current directory may be exhibited:
        #
        #     var p = "../foo/bar".relpath("baz")
-       #     var c = getcwd.basename("")
+       #     var c = getcwd.basename
        #     assert p == "../../{c}/baz"
        #
        # For path resolution independent of the current directory (eg. for paths in URL),
@@ -978,10 +1177,9 @@ redef class String
        #     assert "/fail/does not/exist".rmdir != null
        fun rmdir: nullable Error
        do
-               var res = to_path.rmdir
-               if res then return null
-               var error = new IOError("Cannot change remove `{self}`: {sys.errno.strerror}")
-               return error
+               var p = to_path
+               p.rmdir
+               return p.last_error
        end
 
        # Change the current working directory
@@ -1062,9 +1260,32 @@ redef class String
        end
 end
 
+redef class FlatString
+       redef fun write_native_to(s)
+       do
+               s.write_native(items, first_byte, bytelen)
+       end
+end
+
 redef class NativeString
-       private fun file_exists: Bool is extern "string_NativeString_NativeString_file_exists_0"
-       private fun file_stat: NativeFileStat is extern "string_NativeString_NativeString_file_stat_0"
+       private fun file_exists: Bool `{
+               FILE *hdl = fopen(self,"r");
+               if(hdl != NULL){
+                       fclose(hdl);
+               }
+               return hdl != NULL;
+       `}
+
+       private fun file_stat: NativeFileStat `{
+               struct stat buff;
+               if(stat(self, &buff) != -1) {
+                       struct stat* stat_element;
+                       stat_element = malloc(sizeof(struct stat));
+                       return memcpy(stat_element, &buff, sizeof(struct stat));
+               }
+               return 0;
+       `}
+
        private fun file_lstat: NativeFileStat `{
                struct stat* stat_element;
                int res;
@@ -1073,63 +1294,108 @@ redef class NativeString
                if (res == -1) return NULL;
                return stat_element;
        `}
-       private fun file_mkdir: Bool is extern "string_NativeString_NativeString_file_mkdir_0"
+
+       private fun file_mkdir: Bool `{ return !mkdir(self, 0777); `}
+
        private fun rmdir: Bool `{ return !rmdir(self); `}
-       private fun file_delete: Bool is extern "string_NativeString_NativeString_file_delete_0"
-       private fun file_chdir: Bool is extern "string_NativeString_NativeString_file_chdir_0"
-       private fun file_realpath: NativeString is extern "file_NativeString_realpath"
+
+       private fun file_delete: Bool `{
+               return remove(self) == 0;
+       `}
+
+       private fun file_chdir: Bool `{ return !chdir(self); `}
+
+       private fun file_realpath: NativeString `{ return realpath(self, NULL); `}
 end
 
 # This class is system dependent ... must reify the vfs
 private extern class NativeFileStat `{ struct stat * `}
+
        # Returns the permission bits of file
-       fun mode: Int is extern "file_FileStat_FileStat_mode_0"
+       fun mode: Int `{ return self->st_mode; `}
+
        # Returns the last access time
-       fun atime: Int is extern "file_FileStat_FileStat_atime_0"
+       fun atime: Int `{ return self->st_atime; `}
+
        # Returns the last status change time
-       fun ctime: Int is extern "file_FileStat_FileStat_ctime_0"
+       fun ctime: Int `{ return self->st_ctime; `}
+
        # Returns the last modification time
-       fun mtime: Int is extern "file_FileStat_FileStat_mtime_0"
+       fun mtime: Int `{ return self->st_mtime; `}
+
        # Returns the size
-       fun size: Int is extern "file_FileStat_FileStat_size_0"
+       fun size: Int `{ return self->st_size; `}
 
        # Returns true if it is a regular file (not a device file, pipe, sockect, ...)
        fun is_reg: Bool `{ return S_ISREG(self->st_mode); `}
+
        # Returns true if it is a directory
        fun is_dir: Bool `{ return S_ISDIR(self->st_mode); `}
+
        # Returns true if it is a character device
        fun is_chr: Bool `{ return S_ISCHR(self->st_mode); `}
+
        # Returns true if it is a block device
        fun is_blk: Bool `{ return S_ISBLK(self->st_mode); `}
+
        # Returns true if the type is fifo
        fun is_fifo: Bool `{ return S_ISFIFO(self->st_mode); `}
+
        # Returns true if the type is a link
        fun is_lnk: Bool `{ return S_ISLNK(self->st_mode); `}
+
        # Returns true if the type is a socket
        fun is_sock: Bool `{ return S_ISSOCK(self->st_mode); `}
 end
 
 # Instance of this class are standard FILE * pointers
 private extern class NativeFile `{ FILE* `}
-       fun io_read(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_read_2"
-       fun io_write(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_write_2"
-       fun write_byte(value: Int): Int `{
+       fun io_read(buf: NativeString, len: Int): Int `{
+               return fread(buf, 1, len, self);
+       `}
+
+       fun io_write(buf: NativeString, from, len: Int): Int `{
+               return fwrite(buf+from, 1, len, self);
+       `}
+
+       fun write_byte(value: Byte): Int `{
                unsigned char b = (unsigned char)value;
                return fwrite(&b, 1, 1, self);
        `}
-       fun io_close: Int is extern "file_NativeFile_NativeFile_io_close_0"
-       fun file_stat: NativeFileStat is extern "file_NativeFile_NativeFile_file_stat_0"
+
+       fun io_close: Int `{ return fclose(self); `}
+
+       fun file_stat: NativeFileStat `{
+               struct stat buff;
+               if(fstat(fileno(self), &buff) != -1) {
+                       struct stat* stat_element;
+                       stat_element = malloc(sizeof(struct stat));
+                       return memcpy(stat_element, &buff, sizeof(struct stat));
+               }
+               return 0;
+       `}
+
+       fun ferror: Bool `{ return ferror(self); `}
+
        fun fileno: Int `{ return fileno(self); `}
+
        # Flushes the buffer, forcing the write operation
-       fun flush: Int is extern "fflush"
+       fun flush: Int `{ return fflush(self); `}
+
        # Used to specify how the buffering will be handled for the current stream.
-       fun set_buffering_type(buf_length: Int, mode: Int): Int is extern "file_NativeFile_NativeFile_set_buffering_type_0"
+       fun set_buffering_type(buf_length: Int, mode: Int): Int `{
+               return setvbuf(self, NULL, mode, buf_length);
+       `}
+
+       new io_open_read(path: NativeString) `{ return fopen(path, "r"); `}
+
+       new io_open_write(path: NativeString) `{ return fopen(path, "w"); `}
+
+       new native_stdin `{ return stdin; `}
 
-       new io_open_read(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_read_1"
-       new io_open_write(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_write_1"
-       new native_stdin is extern "file_NativeFileCapable_NativeFileCapable_native_stdin_0"
-       new native_stdout is extern "file_NativeFileCapable_NativeFileCapable_native_stdout_0"
-       new native_stderr is extern "file_NativeFileCapable_NativeFileCapable_native_stderr_0"
+       new native_stdout `{ return stdout; `}
+
+       new native_stderr `{ return stderr; `}
 end
 
 # Standard `DIR*` pointer
@@ -1162,11 +1428,13 @@ redef class Sys
        var stderr: Writer = new Stderr is protected writable, lazy
 
        # Enumeration for buffer mode full (flushes when buffer is full)
-       fun buffer_mode_full: Int is extern "file_Sys_Sys_buffer_mode_full_0"
+       fun buffer_mode_full: Int `{ return _IOFBF; `}
+
        # Enumeration for buffer mode line (flushes when a `\n` is encountered)
-       fun buffer_mode_line: Int is extern "file_Sys_Sys_buffer_mode_line_0"
+       fun buffer_mode_line: Int `{ return _IONBF; `}
+
        # Enumeration for buffer mode none (flushes ASAP when something is written)
-       fun buffer_mode_none: Int is extern "file_Sys_Sys_buffer_mode_none_0"
+       fun buffer_mode_none: Int `{ return _IOLBF; `}
 
        # returns first available stream to read or write to
        # return null on interruption (possibly a signal)
@@ -1192,7 +1460,8 @@ redef class Sys
                end
        end
 
-       private fun intern_poll(in_fds: Array[Int], out_fds: Array[Int]) : nullable Int is extern import Array[Int].length, Array[Int].[], Int.as(nullable Int) `{
+       private fun intern_poll(in_fds: Array[Int], out_fds: Array[Int]): nullable Int
+       import Array[Int].length, Array[Int].[], Int.as(nullable Int) `{
                int in_len, out_len, total_len;
                struct pollfd *c_fds;
                int i;
@@ -1280,5 +1549,6 @@ do
 end
 
 # Return the working (current) directory
-fun getcwd: String do return file_getcwd.to_s
-private fun file_getcwd: NativeString is extern "string_NativeString_NativeString_file_getcwd_0"
+fun getcwd: String do return native_getcwd.to_s
+
+private fun native_getcwd: NativeString `{ return getcwd(NULL, 0); `}