diff --git a/Cargo.toml b/Cargo.toml
index d076b2bec265e22cbf97f81f6d8d76333e311383..98d3bba077c6c409b44e478e835280fb8d8cdb7c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,3 +7,4 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+libc = "0.2"
diff --git a/src/lib.rs b/src/lib.rs
index 31e1bb209f98ec5fc6b7cbea4c4766a555c87247..284f6bb5fdb2415cdb17f29c09c5e95b8dba21a1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,7 +1,336 @@
-#[cfg(test)]
-mod tests {
-    #[test]
-    fn it_works() {
-        assert_eq!(2 + 2, 4);
+use libc;
+use libc::{__errno_location};
+use libc::{c_char, c_int, c_void, size_t, ssize_t};
+
+use std::str;
+use std::ffi::CStr;
+
+/// Get error message string
+///
+/// The `strerror` function returns a string describing the error specified by
+/// the error number `errnum`.
+///
+/// Refer to [strerror(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror.html)
+/// for details.
+///
+/// # Examples
+///
+/// Print error description of of [EACCES][EACCES]:
+/// ```
+/// use ti3_rust::strerror;
+/// use libc::EACCES;
+///
+/// println!("EACCES means: {}", strerror(EACCES));
+/// ```
+///
+/// [EACCES]: ../libc/constant.EACCES.html
+///
+/// # See also
+///
+/// [perror]
+///
+pub fn strerror(errnum: c_int) -> &'static str {
+    let cs = unsafe { CStr::from_ptr(libc::strerror(errnum)) };
+    match cs.to_str() {
+        Err(e) => panic!(e),
+        Ok(s) => s,
     }
 }
+
+/// Write error messages to standard error
+///
+/// The `perror` function looks up the current global error number
+/// [`errno`][get_errno] and maps it to a string description using
+/// [`strerror][strerror], written to the standard error stream in the following
+/// format:
+///
+/// ```test
+/// str: Error description
+/// ```
+///
+/// Refer to [perror(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/perror.html)
+/// for details.
+///
+/// # Examples
+///
+/// Refer to [open].
+///
+/// # See also
+///
+/// [strerror]
+///
+pub fn perror(s: &str) {
+    let errnum = get_errno();
+    eprintln!("{}: {}", s, strerror(errnum));
+}
+
+
+/// Get error return value
+///
+/// The function `get_errno` gets the current global error number `errno`, used
+/// by many functions to specify more detailed errors.
+///
+/// Refer to [errno(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html)
+/// for details.
+///
+/// # Examples
+///
+/// Refer to [perror] and [strerror].
+///
+/// # See also
+///
+/// [perror], [strerror], [**Section 2.3, Error Numbers**](https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_03)
+///
+pub fn get_errno() -> c_int {
+    return unsafe { *__errno_location() };
+}
+
+/// Read from a file
+///
+/// The `read()` function tries to read the number of `nbyte` bytes from the
+/// file specified by the open file descriptor `fildes`. It writes the result
+/// into the buffer `buf` which must at least be of `nbyte` size. The number of
+/// bytes actually read is returned.
+///
+/// Note that even if `nbyte` bytes are available, the function *may* read
+/// less bytes if the operating system so chooses, thus checking the return
+/// value is indespensible. It guarantees a read of 1 byte however (as long as
+/// `nbyte` is greater 0 and there's data to read). If no bytes are available
+/// it returns 0.
+///
+/// Refer to [read(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html)
+/// for details.
+///
+/// # Examples
+///
+/// Read at most 1024 bytes from the standard input and end program if done.
+/// ```
+/// use libc::{ssize_t, size_t, c_int, c_char};
+/// use ti3_rust::read;
+///
+/// const BUFSIZ: size_t = 1024;
+/// const STDIN: c_int = 0; // file descriptor to stdin
+///
+/// let mut buffer: [c_char; BUFSIZ] = [0; BUFSIZ];
+///
+/// let read = match read(STDIN, &mut buffer, BUFSIZ) {
+///     n if n < 0 => panic!("Error reading!"),
+///     0 => return, // End Of File reached
+///     n => n as size_t, // cast ssize_t to size_t as we are definitely non-negative now
+/// };
+/// ```
+///
+/// Read 1024 bytes in a loop from standard input, writing them to standard
+/// output again. Note that [write] doesn't guarantee it wrote all output either.
+/// Additionally we use [strerror] and [get_errno] for error handling.
+/// ```
+/// use libc::{ssize_t, size_t, c_int, c_char};
+/// use ti3_rust::{read,write,strerror,get_errno};
+///
+/// const BUFSIZ: size_t = 1024;
+/// const STDIN: c_int = 0; // file descriptor to stdin
+/// const STDOUT: c_int = 1; // file descriptor to stdout
+///
+/// loop {
+///     let mut buffer: [c_char; BUFSIZ] = [0; BUFSIZ];
+///
+///     let read = match read(STDIN, &mut buffer, BUFSIZ) {
+///         n if n < 0 => panic!("write: {}", strerror(get_errno())),
+///         0 => return, // End Of File reached
+///         n => n as size_t, // cast ssize_t to size_t as we are definitely non-negative now
+///     };
+///
+///     let mut remaining = read;
+///     while remaining > 0 {
+///         let written = match write(STDOUT, &buffer[BUFSIZ - remaining..], remaining) {
+///              n if n < 0 => panic!("read: {}", strerror(get_errno())),
+///              n => n as size_t,
+///         };
+///         remaining -= written;
+///     }
+/// }
+/// ```
+///
+/// # Return Value
+///
+/// If reading the file was successful, the non-negative number of bytes read
+/// shall be returned. This may be less than requested, either if the file
+/// doesn't have enough bytes or the operating system chooses to do so.
+///
+/// Otherwise -1 is returned and [`errno`][get_errno] is set to an error value
+/// describing the error.
+///
+/// # Errors
+///
+/// The function may *at least* fail with the following error(s):
+///
+/// * [EBADF][EBADF]: The `fildes` argument is not a valid file descriptor open
+///   for reading.
+///
+/// [EBADF]: ../libc/constant.EBADF.html
+///
+/// # See also
+///
+/// [open], [write]
+///
+pub fn read(fildes: c_int, mut buf: &mut [c_char], nbyte: size_t) -> ssize_t {
+    let buf: *mut c_void = &mut buf as *mut _ as *mut c_void;
+    return unsafe { libc::read(fildes, buf, nbyte) };
+}
+
+/// Write to a file
+///
+/// The `write()` function tries to write the number of `nbyte` bytes to the
+/// file specified by the open file descriptor `fildes`. The data is read from
+/// the buffer `buf` which must at least be of `nbyte` size.
+///
+/// Note that even if space for `nbyte` bytes is available, the function *may* write
+/// less bytes if the operating system so chooses, thus checking the return
+/// value is indespensible. It guarantees a write of 1 byte however (as long as
+/// `nbyte` is greater 0 and there's enough space). If all bytes are written
+/// it returns `nbyte`.
+///
+/// Refer to [write(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html)
+/// for details.
+///
+/// # Examples
+///
+/// Refer to [read]
+///
+/// # Return Value
+///
+/// If writing the file was successful, the non-negative number of bytes written
+/// shall be returned. This may be less than requested, if the file
+/// the operating system chooses to do so.
+///
+/// Otherwise -1 is returned and [`errno`][get_errno] is set to an error value
+/// describing the error.
+///
+/// # Errors
+///
+/// The function may *at least* fail with the following error(s):
+///
+/// * [EBADF][EBADF]: The `fildes` argument is not a valid file descriptor open
+///   for reading.
+/// * [ENOSPC][ENOSPC]: There was no free space remaining on the device containing the file.
+///
+/// [EBADF]: ../libc/constant.EBADF.html
+/// [ENOSPC]: ../libc/constant.ENOSPC.html
+///
+/// # See also
+///
+/// [open], [read]
+///
+pub fn write(fildes: c_int, mut buf: &[c_char], nbyte: size_t) -> ssize_t {
+    let buf: *mut c_void = &mut buf as *mut _ as *mut c_void;
+    return unsafe { libc::write(fildes, buf, nbyte) };
+}
+
+
+
+/// Open file
+///
+/// The `open()` function creates a connection between a file named by the
+/// pathname `path` and a file descriptor which it creates and returns. The file
+/// descriptor can then be used by other I/O functions to access that file.
+///
+/// Refer to [open(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html)
+/// for details
+///
+/// # Examples
+///
+/// Open a file by reading and panic otherwise.
+/// ```should_panic
+/// use libc::O_RDONLY;
+/// use ti3_rust::open;
+///
+/// let pathname = "/path/to/some/file";
+/// let fd = match open(pathname, O_RDONLY) {
+///     -1 => panic!("{}: Couldn't open", pathname),
+///     n  => n,
+/// };
+/// // ...
+/// ```
+///
+/// Like above, but print more verbose error message using [perror] and Don't Panic!
+/// Finally the file descriptor is deallocated using [close]
+/// ```
+/// use libc::O_RDONLY;
+/// use ti3_rust::{open,perror,close};
+///
+/// let pathname = "/path/to/some/file";
+/// let fd = open(pathname, O_RDONLY);
+/// if fd == -1 {
+///     perror(pathname);
+///     return;
+/// }
+/// // ...
+/// close(fd);
+/// ```
+///
+/// # Return Value
+///
+/// If opening the file succeeded, a non-negative integer will be returned.
+/// Otherwise -1 is returned and [`errno`][get_errno] set accordingly.
+///
+/// # Errors
+///
+/// The value [`errno`][get_errno] can assume *at least* the following values:
+///
+/// * [EACCES][EACCES]: Accessing either a component of the path prefix or the
+///   file itself isn't allowed with the specified `oflags`.
+///
+/// * [ENOENT][ENOENT]: Either the path specified by `path` does not name
+///   an existing file while O_CREAT is set, or O_CREAT is set but a component
+///   of the path prefix does not name an existing directory, or `path` is an
+///   empty string.
+///
+/// * [EINVAL][EINVAL]: The value of the `oflag` argument is not valid.
+///
+/// [EACCES]: ../libc/constant.EACCES.html
+/// [ENOENT]: ../libc/constant.ENOENT.html
+/// [EINVAL]: ../libc/constant.EINVAL.html
+///
+pub fn open(path: &str, oflag: c_int) -> c_int {
+    let path = path.as_bytes().as_ptr() as *const i8;
+    return unsafe { libc::open(path, oflag) };
+}
+
+//pub fn open_with_mode(path: &str, oflag: c_int, mode: mode_t) -> c_int {
+//    let path = path.as_bytes().as_ptr() as *const i8;
+//    return unsafe { libc::open(path, oflag, mode) };
+//}
+
+
+/// Close a file descriptor
+///
+/// The `close()` function shall deallocate the file descriptor `fildes`.
+///
+/// Refer to [close(3p)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html)
+/// for details.
+///
+/// # Examples
+///
+/// Refer to [open].
+///
+/// # Return Value
+///
+/// If the file descriptor could successfully be deallocated, 0 is returned.
+/// Otherwise -1 is returned and [`errno`][get_errno] set accordingly.
+///
+/// # Errors
+///
+/// The value [`errno`][get_errno] can be set to *at least* the value(s):
+///
+/// * [EBADF][EBADF]: The value `fildes` does not refer to a valid open file
+///   descriptor.
+///
+/// [EBADF]: ../libc/constant.EBADF.html
+///
+/// # See also
+///
+/// [open]
+///
+pub fn close(fildes: c_int) -> c_int {
+    return unsafe { libc::close(fildes) };
+}