diff --git a/src/Storm/FileSystem/Abstract.php b/src/Storm/FileSystem/Abstract.php
new file mode 100644
index 0000000000000000000000000000000000000000..397b679d9ffed3e91124808303f9d632b520b725
--- /dev/null
+++ b/src/Storm/FileSystem/Abstract.php
@@ -0,0 +1,54 @@
+<?php
+/*
+STORM is under the MIT License (MIT)
+
+Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+
+abstract class Storm_FileSystem_Abstract {
+  abstract public function fileExists($path);
+
+  abstract public function fileGetContents($path);
+
+  abstract public function fileGetSize($path);
+
+  abstract public function filePutContents($path, $data);
+
+  abstract public function fileMTime(string $path);
+
+  abstract public function directoryNamesAt($path);
+
+  abstract public function fileNamesAt($path);
+
+  abstract public function rename(string $from, string $to) : bool;
+
+  abstract public function isReadable(string $path) : bool;
+
+  abstract public function isWritable(string $path) : bool;
+
+  abstract public function fileGetContentAsArray(string $path);
+
+  abstract public function delete(string $path) : bool;
+
+  abstract public function readfile(string $path);
+}
diff --git a/src/Storm/FileSystem/Disk.php b/src/Storm/FileSystem/Disk.php
index 20d36aece1c7878bc9b98bdc94e60ec215c6920c..452ad0e2f29a34bf299a607d9f88b0253bd744f8 100644
--- a/src/Storm/FileSystem/Disk.php
+++ b/src/Storm/FileSystem/Disk.php
@@ -24,7 +24,7 @@ THE SOFTWARE.
 
 */
 
-class Storm_FileSystem_Disk {
+class Storm_FileSystem_Disk extends Storm_FileSystem_Abstract {
   public function fileExists($path) {
     return file_exists($path);
   }
@@ -46,6 +46,41 @@ class Storm_FileSystem_Disk {
   }
 
 
+  public function fileMTime(string $path) {
+    return filemtime($path);
+  }
+
+
+  public function rename(string $from, string $to) : bool {
+    return rename($from, $to);
+  }
+
+
+  public function isReadable(string $path) : bool {
+    return is_readable($path);
+  }
+
+
+  public function isWritable(string $path) : bool {
+    return is_writable($path);
+  }
+
+
+  public function fileGetContentAsArray(string $path) {
+    return file($path);
+  }
+
+
+  public function delete(string $path) : bool {
+    return unlink($path);
+  }
+
+
+  public function readfile(string $path) {
+    return readfile($path);
+  }
+
+
   public function directoryNamesAt($path) {
     if (!file_exists($path))
       return [];
diff --git a/src/Storm/FileSystem/Volatile.php b/src/Storm/FileSystem/Volatile.php
index 96375333af631fc56b6d63c2d293f7e22d76833e..d9623b75df35be2f9acdb85b1498ebb10903d0f5 100644
--- a/src/Storm/FileSystem/Volatile.php
+++ b/src/Storm/FileSystem/Volatile.php
@@ -24,7 +24,7 @@ THE SOFTWARE.
 
 */
 
-class Storm_FileSystem_Volatile {
+class Storm_FileSystem_Volatile extends Storm_FileSystem_Abstract {
   protected $_current_directory;
 
   public function __construct() {
@@ -46,9 +46,9 @@ class Storm_FileSystem_Volatile {
   }
 
 
-  public function touch($path) {
+  public function touch($path, $init_callback=null) {
     if ($directory = $this->asPath(dirname($path))->asDirectory())
-      $directory->touch(basename($path));
+      $directory->touch(basename($path), $init_callback);
     return $this;
   }
 
@@ -79,12 +79,66 @@ class Storm_FileSystem_Volatile {
   }
 
 
+  public function fileMTime(string $path) {
+    return $this->fileExists($path)
+      ? $this->entryAt($path)->getMTime()
+      : 0;
+  }
+
+
+  public function rename(string $from, string $to) : bool {
+    if (!$this->fileExists($from))
+      return false;
+
+    $this->filePutContents($to, $this->fileGetContents($from))
+         ->rm($from);
+
+    return true;
+  }
+
+
+  public function isReadable(string $path) : bool {
+    return $this->fileExists($path);
+  }
+
+
+  public function isWritable(string $path) : bool {
+    return $this->fileExists($path);
+  }
+
+
+  public function fileGetContentAsArray(string $path) {
+    if (null === $content = $this->fileGetContents($path))
+      return false;
+
+    $lines = explode("\n", $content);
+
+    return array_map(fn($line) => $line . "\n",
+                     $lines);
+  }
+
+
   public function rm($path) {
     $this->asPath($path)->rm();
     return $this;
   }
 
 
+  public function delete(string $path) : bool {
+    $this->rm($path);
+    return !$this->isReadable($path);
+  }
+
+
+  public function readfile(string $path) {
+    if (!$this->isReadable($path))
+      return false;
+
+    echo $this->fileGetContents($path);
+    return $this->fileGetSize($path);
+  }
+
+
   public function asPath($path) {
     return (new Storm_FileSystem_Volatile_Path($path, $this->_current_directory));
   }
@@ -107,10 +161,8 @@ class Storm_FileSystem_Volatile {
 
 
   public function fileNamesAt($path) {
-    $collection = $this->entryAt($path)->select(
-      function ($entry) {
-        return $entry->isFile();
-      });
+    $collection = $this->entryAt($path)
+                       ->select(fn($entry) => $entry->isFile());
 
     array_walk($collection, function (&$entry) {$entry = $entry->getName();});
     return $collection;
@@ -217,7 +269,8 @@ class Storm_FileSystem_Volatile_Path {
 abstract class Storm_FileSystem_Volatile_Entry {
   protected
     $_name,
-    $_parent;
+    $_parent,
+    $_mtime;
 
   public function __construct($name, $parent) {
     $this->_name = $name;
@@ -246,7 +299,20 @@ abstract class Storm_FileSystem_Volatile_Entry {
   }
 
 
+  public function getMTime() : int {
+    return $this->_mtime ?? 0;
+  }
+
+
+  public function setMTime(int $mtime) : self {
+    $this->_mtime = $mtime;
+    return $this;
+  }
+
+
   abstract function isDirectory();
+
+  abstract function isFile();
 }
 
 
@@ -273,6 +339,9 @@ class Storm_FileSystem_Volatile_Directory extends Storm_FileSystem_Volatile_Entr
 
 
   public function getSubdirectoryNamed($name) {
+    if ('..' === $name)
+      return $this->_parent ?? $this;
+
     return $this->fileExists($name) ? $this->_entries[$name] : null;
   }
 
@@ -284,8 +353,11 @@ class Storm_FileSystem_Volatile_Directory extends Storm_FileSystem_Volatile_Entr
   }
 
 
-  public function touch($name) {
-    $this->_entries [$name]= new Storm_FileSystem_Volatile_File($name, $this);
+  public function touch($name, $init_callback=null) {
+    $this->_entries[$name] = $entry = new Storm_FileSystem_Volatile_File($name, $this);
+    if (null !== $init_callback)
+      $init_callback($entry);
+
     return $this;
   }
 
@@ -310,13 +382,20 @@ class Storm_FileSystem_Volatile_Directory extends Storm_FileSystem_Volatile_Entr
   public function isDirectory() {
     return true;
   }
+
+
+  public function isFile() {
+    return false;
+  }
 }
 
 
 
 
 class Storm_FileSystem_Volatile_File extends Storm_FileSystem_Volatile_Entry {
-  protected $_contents = '';
+  protected
+    $_contents,
+    $_size;
 
   public function isDirectory() {
     return false;
@@ -330,18 +409,24 @@ class Storm_FileSystem_Volatile_File extends Storm_FileSystem_Volatile_Entry {
 
   public function putContents($contents) {
     $this->_contents = $contents;
+    return $this;
   }
 
 
   public function getContents() {
-    return $this->_contents;
+    return $this->_contents ?? '';
   }
 
 
   public function getSize() {
-    return strlen($this->_contents);
+    return null !== $this->_size
+      ? $this->_size
+      : strlen($this->_contents);
   }
-}
 
 
-?>
\ No newline at end of file
+  public function setSize(int $size) : self {
+    $this->_size = $size;
+    return $this;
+  }
+}
diff --git a/tests/Storm/FileSystem/VolatileTest.php b/tests/Storm/FileSystem/VolatileTest.php
index 91a091f52ffec7cb0b75e87d289a55725f821771..730691039196d735a6a2d8206fb846b130e6533c 100644
--- a/tests/Storm/FileSystem/VolatileTest.php
+++ b/tests/Storm/FileSystem/VolatileTest.php
@@ -25,10 +25,22 @@ THE SOFTWARE.
 */
 
 
-class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase {
+abstract class Storm_FileSystem_VolatileTestCase extends PHPUnit_Framework_TestCase {
+  protected Storm_FileSystem_Volatile $_fs;
+
+  public function setUp() {
+    parent::setUp();
+    $this->_fs = new Storm_FileSystem_Volatile;
+  }
+}
+
+
+
+
+class Storm_FileSystem_VolatileBasicTest extends Storm_FileSystem_VolatileTestCase {
   public function setUp() {
     parent::setUp();
-    $this->_fs = (new Storm_FileSystem_Volatile())
+    $this->_fs
       ->mkdir('/public/bokeh/skins')
       ->cd('/public/bokeh/skins')
       ->mkdir('dark')
@@ -36,7 +48,7 @@ class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase {
       ->touch('.htaccess')
 
       ->cd('light')
-      ->touch('style.css')
+      ->touch('style.css', fn($entry) => $entry->setMTime(22))
 
       ->cd('/')
       ->mkdir('etc/')
@@ -67,6 +79,13 @@ class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase {
   }
 
 
+  /** @test */
+  public function notesTxtShouldNoLongerExistAfterDelete() {
+    $this->_fs->delete('/var/notes.txt');
+    $this->assertFalse($this->_fs->fileExists('/var/notes.txt'));
+  }
+
+
   /** @test */
   public function ghostGetContentsShouldAnswersNull() {
     $this->assertNull($this->_fs->fileGetContents('ghost.txt'));
@@ -79,6 +98,42 @@ class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase {
   }
 
 
+  /** @test */
+  public function styleCssMtimeShouldBe22() {
+    $this->assertEquals(22, $this->_fs->fileMTime('/public/bokeh/skins/light/style.css'));
+  }
+
+
+  /** @test */
+  public function notExistingFileMtimeShouldBe0() {
+    $this->assertEquals(0, $this->_fs->fileMTime('/I/do/not/exist'));
+  }
+
+
+  /** @test */
+  public function styleCssShouldBeReadable() {
+    $this->assertTrue($this->_fs->isReadable('/public/bokeh/skins/light/style.css'));
+  }
+
+
+  /** @test */
+  public function notExistingFileShouldNotBeReadable() {
+    $this->assertFalse($this->_fs->isReadable('/I/do/not/exist'));
+  }
+
+
+  /** @test */
+  public function styleCssShouldBeWritable() {
+    $this->assertTrue($this->_fs->isWritable('/public/bokeh/skins/light/style.css'));
+  }
+
+
+  /** @test */
+  public function notExistingFileShouldNotBeWritable() {
+    $this->assertFalse($this->_fs->isWritable('/I/do/not/exist'));
+  }
+
+
   public function validPaths() {
     return [
       ['/etc'],
@@ -90,7 +145,7 @@ class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase {
       ['/var/www'],
       ['php/php.ini'],
       ['./php/php.ini'],
-//      ['../etc/php/php.ini'],
+      ['../etc/php/php.ini'],
       ['/public/bokeh/skins/light/style.css']
       ];
   }
@@ -138,4 +193,115 @@ class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase {
     $dirs = $this->_fs->directoryNamesAt('/public/bokeh/skins');
     $this->assertEquals(['dark' => 'dark', 'light' => 'light'], $dirs);
   }
+}
+
+
+
+
+class Storm_FileSystem_VolatileRenameTest extends Storm_FileSystem_VolatileTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->_fs
+      ->mkdir('/testing/transfert')
+      ->filePutContents('/testing/transfert/mysuperfile', 'my super content')
+
+      ->mkdir('/testing/integration')
+      ;
+  }
+
+
+  /** @test */
+  public function renameNotExistingFileShouldDoNothing() {
+    $this->_fs->rename('/I/do/not/exist', '/testing/integration/newname.txt');
+    $this->assertFalse($this->_fs->fileExists('/testing/integration/newname.txt'));
+  }
+
+
+  /** @test */
+  public function renamedMysuperfileToNewnameTxtShouldContainsMySuperContent() {
+    $this->_fs->rename('/testing/transfert/mysuperfile', '/testing/integration/newname.txt');
+    $this->assertEquals('my super content',
+                        $this->_fs->fileGetContents('/testing/integration/newname.txt'));
+  }
+}
+
+
+
+
+class Storm_FileSystem_VolatileGetContentsAsArrayTest extends Storm_FileSystem_VolatileTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->_fs
+      ->mkdir('/testing/transfert')
+      ->filePutContents('/testing/transfert/mysuperfile',
+                        implode("\n", ['first line', 'second line']))
+      ;
+  }
+
+
+  /** @test */
+  public function contentAsArrayOfNotExistingFileShouldBeFalse() {
+    $this->assertFalse($this->_fs->fileGetContentAsArray('/I/do/not/exist'));
+  }
+
+
+  /** @test */
+  public function mysuperfileContentAsArrayShouldBeFirstAndSecondLine() {
+    $this->assertEquals(["first line\n", "second line\n"],
+                        $this->_fs->fileGetContentAsArray('/testing/transfert/mysuperfile'));
+  }
+}
+
+
+
+
+class Storm_FileSystem_VolatileReadfileTest extends Storm_FileSystem_VolatileTestCase {
+  protected $_buffer;
+
+  public function setUp() {
+    parent::setUp();
+    $this->_fs
+      ->mkdir('/testing/transfert')
+      ->filePutContents('/testing/transfert/mysuperfile', '>>>> Mega super content <<<<')
+      ;
+  }
+
+
+  protected function _readFileWithBuffering($path) {
+    ob_start();
+    try {
+      $result = $this->_fs->readfile($path);
+      $this->_buffer = ob_get_clean();
+      return $result;
+    } catch(Exception $e) {
+      ob_end_clean();
+      throw $e;
+    }
+  }
+
+
+  /** @test */
+  public function readFileOfNotExistingFileShouldBeFalse() {
+    $this->assertFalse($this->_readFileWithBuffering('/I/do/not/exist'));
+  }
+
+
+  /** @test */
+  public function readFileOfNotExistingFileShouldOutputNothing() {
+    $this->_readFileWithBuffering('/I/do/not/exist');
+    $this->assertEquals('', $this->_buffer);
+  }
+
+
+  /** @test */
+  public function readFileOfMysuperfileShouldBeContentLength() {
+    $this->assertEquals(28, $this->_readFileWithBuffering('/testing/transfert/mysuperfile'));
+  }
+
+
+  /** @test */
+  public function readFileOfMysuperfileShouldOutputMegaSuperContent() {
+    $this->_readFileWithBuffering('/testing/transfert/mysuperfile');
+    $this->assertEquals('>>>> Mega super content <<<<', $this->_buffer);
+  }
 }
\ No newline at end of file