diff --git a/ev3dev.cpp b/ev3dev.cpp
index 6237e48..8f97efe 100644
--- a/ev3dev.cpp
+++ b/ev3dev.cpp
@@ -27,7 +27,6 @@
 
 #include <iostream>
 #include <sstream>
-#include <fstream>
 #include <list>
 #include <map>
 #include <array>
@@ -38,7 +37,8 @@
 #include <thread>
 #include <stdexcept>
 #include <string.h>
-#include <math.h>
+#include <cmath>
+#include <cstdio>
 
 #include <dirent.h>
 #include <sys/mman.h>
@@ -66,6 +66,160 @@ static const int bits_per_long = sizeof(long) * 8;
 namespace ev3dev {
 namespace {
 
+//-----------------------------------------------------------------------------
+class file_reader {
+    public:
+        file_reader() : f(0) {}
+
+        file_reader(file_reader &&other)
+            : fname(std::move(other.fname)), f(other.f)
+        {
+            other.f = 0;
+        }
+
+        ~file_reader() {
+            if (f) fclose(f);
+        }
+
+        bool is_open() const {
+            return f != 0;
+        }
+
+        void open(const std::string &_fname) {
+            fname = _fname;
+            f = fopen(fname.c_str(), "r");
+            setbuf(f, NULL);
+
+            if (!f) {
+                throw std::system_error(std::make_error_code(static_cast<std::errc>(errno)),
+                        std::string("Failed to open \"") + fname + "\"");
+            }
+        }
+
+        std::string get_string() {
+            char buf[256];
+            try_read([this, &buf](){ return 1 == fscanf(f, "%255s", buf); });
+            return buf;
+        }
+
+        std::string get_line() {
+            char buf[256];
+            try_read([this, &buf](){
+                    if (!fgets(buf, 255, f)) return false;
+                    char *pos;
+                    if ((pos=strchr(buf, '\n')) != NULL) *pos = '\0';
+                    return true;
+                    });
+            return buf;
+        }
+
+        int get_int() {
+            int v;
+            try_read([this, &v](){return 1 == fscanf(f, "%d", &v); });
+            return v;
+        }
+
+        void get_data(char *data, size_t count) {
+            try_read([this, &data, count](){ return count == fread(data, count, count, f); });
+        }
+    private:
+        std::string fname;
+        FILE *f;
+
+        void reopen() {
+            if (f) fclose(f);
+            f = fopen(fname.c_str(), "r");
+            setbuf(f, NULL);
+        }
+
+        template <class Callable>
+        void try_read(Callable w) {
+            for(int attempt = 0; attempt < 2; ++attempt) {
+                fseek(f, 0, SEEK_SET);
+
+                if (w()) return;
+
+                // Failed to read the value.
+                // This could mean the sysfs attribute was recreated and the
+                // corresponding file handle got stale. Lets close the file and try
+                // again (once):
+                if (attempt != 0) {
+                    throw std::system_error(std::make_error_code(static_cast<std::errc>(errno)), fname);
+                }
+
+                reopen();
+            }
+        }
+};
+
+//-----------------------------------------------------------------------------
+class file_writer {
+    public:
+        file_writer() : f(0) {}
+
+        file_writer(file_writer &&other)
+            : fname(std::move(other.fname)), f(other.f)
+        {
+            other.f = 0;
+        }
+
+        ~file_writer() {
+            if (f) fclose(f);
+        }
+
+        bool is_open() const {
+            return f != 0;
+        }
+
+        void open(const std::string &_fname) {
+            fname = _fname;
+            f = fopen(fname.c_str(), "w");
+            setbuf(f, NULL);
+
+            if (!f) {
+                throw std::system_error(std::make_error_code(static_cast<std::errc>(errno)),
+                        std::string("Failed to open \"") + fname + "\"");
+            }
+        }
+
+        void put_string(const std::string &v) {
+            try_write([this, &v](){ return EOF != fputs(v.c_str(), f); });
+        }
+
+        void put_int(int v) {
+            try_write([this, v](){ return fprintf(f, "%d", v) >= 0; });
+        }
+
+    private:
+        std::string fname;
+        FILE *f;
+
+        void reopen() {
+            if (f) fclose(f);
+            f = fopen(fname.c_str(), "w");
+            setbuf(f, NULL);
+        }
+
+        template <class Callable>
+        void try_write(Callable w) {
+            for(int attempt = 0; attempt < 2; ++attempt) {
+                fseek(f, 0, SEEK_SET);
+
+                if (w()) return;
+
+                // Failed to write the value.
+                // This could mean the sysfs attribute was recreated and the
+                // corresponding file handle got stale. Lets close the file and try
+                // again (once):
+                if (attempt != 0) {
+                    throw std::system_error(std::make_error_code(static_cast<std::errc>(errno)), fname);
+                }
+
+                reopen();
+            }
+        }
+};
+
 // This class implements a small LRU cache. It assumes the number of elements
 // is small, and so uses a simple linear search.
 template <typename K, typename V>
@@ -118,46 +272,24 @@ class lru_cache {
 };
 
 // A global cache of files.
-std::ifstream& ifstream_cache(const std::string &path) {
-    static lru_cache<std::string, std::ifstream> cache(FSTREAM_CACHE_SIZE);
+file_reader& reader_cache(const std::string &path) {
+    static lru_cache<std::string, file_reader> cache(FSTREAM_CACHE_SIZE);
     static std::mutex mx;
 
     std::lock_guard<std::mutex> lock(mx);
-    return cache[path];
+    file_reader &f = cache[path];
+    if (!f.is_open()) f.open(path);
+    return f;
 }
 
-std::ofstream& ofstream_cache(const std::string &path) {
-    static lru_cache<std::string, std::ofstream> cache(FSTREAM_CACHE_SIZE);
+file_writer& writer_cache(const std::string &path) {
+    static lru_cache<std::string, file_writer> cache(FSTREAM_CACHE_SIZE);
     static std::mutex mx;
 
     std::lock_guard<std::mutex> lock(mx);
-    return cache[path];
-}
-
-//-----------------------------------------------------------------------------
-std::ofstream &ofstream_open(const std::string &path) {
-    std::ofstream &file = ofstream_cache(path);
-    if (!file.is_open()) {
-        // Don't buffer writes to avoid latency. Also saves a bit of memory.
-        file.rdbuf()->pubsetbuf(NULL, 0);
-        file.open(path);
-    } else {
-        // Clear the error bits in case something happened.
-        file.clear();
-    }
-    return file;
-}
-
-std::ifstream &ifstream_open(const std::string &path) {
-    std::ifstream &file = ifstream_cache(path);
-    if (!file.is_open()) {
-        file.open(path);
-    } else {
-        // Clear the flags bits in case something happened (like reaching EOF).
-        file.clear();
-        file.seekg(0, std::ios::beg);
-    }
-    return file;
+    file_writer &f = cache[path];
+    if (!f.is_open()) f.open(path);
+    return f;
 }
 
 } // namespace
@@ -243,25 +375,7 @@ int device::get_attr_int(const std::string &name) const {
     if (_path.empty())
         throw system_error(make_error_code(errc::function_not_supported), "no device connected");
 
-    for(int attempt = 0; attempt < 2; ++attempt) {
-        ifstream &is = ifstream_open(_path + name);
-        if (is.is_open()) {
-            int result = 0;
-            try {
-                is >> result;
-                return result;
-            } catch(...) {
-                // This could mean the sysfs attribute was recreated and the
-                // corresponding file handle got stale. Lets close the file and try
-                // again (once):
-                if (attempt != 0) throw;
-
-                is.close();
-                is.clear();
-            }
-        } else break;
-    }
-    throw system_error(make_error_code(errc::no_such_device), _path+name);
+    return reader_cache(_path + name).get_int();
 }
 
 //-----------------------------------------------------------------------------
@@ -271,23 +385,7 @@ void device::set_attr_int(const std::string &name, int value) {
     if (_path.empty())
         throw system_error(make_error_code(errc::function_not_supported), "no device connected");
 
-    for(int attempt = 0; attempt < 2; ++attempt) {
-        ofstream &os = ofstream_open(_path + name);
-        if (os.is_open()) {
-            if (os << value) return;
-
-            // An error could mean that sysfs attribute was recreated and the cached
-            // file handle is stale. Lets close the file and try again (once):
-            if (attempt == 0 && errno == ENODEV) {
-                os.close();
-                os.clear();
-            } else {
-                throw system_error(std::error_code(errno, std::system_category()));
-            }
-        } else {
-            throw system_error(make_error_code(errc::no_such_device), _path + name);
-        }
-    }
+    writer_cache(_path + name).put_int(value);
 }
 
 //-----------------------------------------------------------------------------
@@ -297,14 +395,7 @@ std::string device::get_attr_string(const std::string &name) const {
     if (_path.empty())
         throw system_error(make_error_code(errc::function_not_supported), "no device connected");
 
-    ifstream &is = ifstream_open(_path + name);
-    if (is.is_open()) {
-        string result;
-        is >> result;
-        return result;
-    }
-
-    throw system_error(make_error_code(errc::no_such_device), _path+name);
+    return reader_cache(_path + name).get_string();
 }
 
 //-----------------------------------------------------------------------------
@@ -314,13 +405,7 @@ void device::set_attr_string(const std::string &name, const std::string &value)
     if (_path.empty())
         throw system_error(make_error_code(errc::function_not_supported), "no device connected");
 
-    ofstream &os = ofstream_open(_path + name);
-    if (os.is_open()) {
-        if (!(os << value)) throw system_error(std::error_code(errno, std::system_category()));
-        return;
-    }
-
-    throw system_error(make_error_code(errc::no_such_device), _path+name);
+    writer_cache(_path + name).put_string(value);
 }
 
 //-----------------------------------------------------------------------------
@@ -330,14 +415,7 @@ std::string device::get_attr_line(const std::string &name) const {
     if (_path.empty())
         throw system_error(make_error_code(errc::function_not_supported), "no device connected");
 
-    ifstream &is = ifstream_open(_path + name);
-    if (is.is_open()) {
-        string result;
-        getline(is, result);
-        return result;
-    }
-
-    throw system_error(make_error_code(errc::no_such_device), _path+name);
+    return reader_cache(_path + name).get_line();
 }
 
 //-----------------------------------------------------------------------------
@@ -513,14 +591,8 @@ const std::vector<char>& sensor::bin_data() const {
         _bin_data.resize(num_values() * value_size);
     }
 
-    const string fname = _path + "bin_data";
-    ifstream &is = ifstream_open(fname);
-    if (is.is_open()) {
-        is.read(_bin_data.data(), _bin_data.size());
-        return _bin_data;
-    }
-
-    throw system_error(make_error_code(errc::no_such_device), fname);
+    reader_cache(_path + "bin_data").get_data(_bin_data.data(), _bin_data.size());
+    return _bin_data;
 }
 
 //-----------------------------------------------------------------------------