add union fs
diff --git a/README.md b/README.md
index 67df853..c5f33fc 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@
 * Support for compositional file systems by joining various different file systems (see httpFs)
 * Filtering of calls to intercept opening / modifying files, several filters
 may be stacked.
+* Unions of filesystems to overlay two filesystems. These may be stacked.
 * A set of utility functions ported from io, ioutil & hugo to be afero aware
 
 
@@ -313,6 +314,36 @@
 provide a filtered view on file names, any file (not directory) NOT matching
 the passed regexp will be treated as non-existing
 
+## Unions
+
+Afero has the possibilty to overlay two filesystems as a union, these are
+special types of filters. To create a new union Fs use the `NewUnionFs()`. The
+example below creates an memory cache for the OsFs:
+```go
+    ufs := NewUnionFs(&OsFs{}, &MemMapFs{}, NewCacheUnionFs(1 * time.Minute))
+```
+
+Available UnionFs are:
+
+### NewCacheUnionFs(time.Duration)
+
+Cache files in the layer for the given time.Duration, a cache duration of 0
+means "forever".
+
+If the base filesystem is writeable, any changes to files will be done first
+to the base, then to the overlay layer. Write calls to open file handles
+like `Write()` or `Truncate()` to the overlay first.
+
+A read-only base will make the overlay also read-only but still copy files
+from the base to the overlay when they're not present (or outdated) in the
+caching layer.
+
+### NewCoWUnionFs()
+
+A CopyOnWrite union: any attempt to modify a file in the base will copy
+the file to the overlay layer before modification. This overlay layer is
+currently limited to MemMapFs.
+
 # About the project
 
 ## What's in the name
diff --git a/union.go b/union.go
new file mode 100644
index 0000000..bd6cff4
--- /dev/null
+++ b/union.go
@@ -0,0 +1,280 @@
+package afero
+
+import (
+	"io"
+	"os"
+	"path/filepath"
+	"syscall"
+)
+
+type UnionFs func(Fs) FilterFs
+
+// Create a new UnionFs:
+//
+//    ufs := NewUnionFs(baseFs, layerFs, NewCoWUnionFs())
+//    cfs := NewUnionFs(baseFs, layerFs, NewCacheUnionFs(cacheTime))
+func NewUnionFs(base Fs, overlay Fs, impl UnionFs) Fs {
+	ufs := impl(overlay)
+	ufs.SetSource(base)
+	return ufs
+}
+
+func copyToLayer(base Fs, layer Fs, name string) error {
+	bfh, err := base.Open(name)
+	if err != nil {
+		return err
+	}
+	defer bfh.Close()
+
+	exists, err := Exists(layer, filepath.Dir(name))
+	if err != nil {
+		return err
+	}
+	if !exists {
+		err = layer.MkdirAll(filepath.Dir(name), 0777) // FIXME?
+		if err != nil {
+			return err
+		}
+	}
+
+	lfh, err := layer.Create(name)
+	if err != nil {
+		return err
+	}
+	n, err := io.Copy(lfh, bfh)
+	if err != nil {
+		layer.Remove(name)
+		lfh.Close()
+		return err
+	}
+
+	bfi, err := bfh.Stat()
+	if err != nil || bfi.Size() != n {
+		layer.Remove(name)
+		lfh.Close()
+		return syscall.EIO
+	}
+
+	err = lfh.Close()
+	if err != nil {
+		layer.Remove(name)
+		lfh.Close()
+		return err
+	}
+	return layer.Chtimes(name, bfi.ModTime(), bfi.ModTime())
+}
+
+// The UnionFile implements the afero.File interface and will be returned
+// when reading a directory present at least in the overlay or opening a file
+// for writing.
+//
+// The calls to
+// Readdir() and Readdirnames() merge the file os.FileInfo / names from the
+// base and the overlay - for files present in both layers, only those
+// from the overlay will be used.
+//
+// When opening files for writing (Create() / OpenFile() with the right flags)
+// the operations will be done in both layers, starting with the overlay. A
+// successful read in the overlay will move the cursor position in the base layer
+// by the number of bytes read.
+type UnionFile struct {
+	layer File
+	base  File
+	off   int
+	files []os.FileInfo
+}
+
+func (f *UnionFile) Close() error {
+	// first close base, so we have a newer timestamp in the overlay. If we'd close
+	// the overlay first, we'd get a cacheStale the next time we access this file
+	// -> cache would be useless ;-)
+	if f.base != nil {
+		f.base.Close()
+	}
+	if f.layer != nil {
+		return f.layer.Close()
+	}
+	return syscall.EBADFD
+}
+
+func (f *UnionFile) Read(s []byte) (int, error) {
+	if f.layer != nil {
+		n, err := f.layer.Read(s)
+		if (err == nil || err == io.EOF) && f.base != nil {
+			// advance the file position also in the base file, the next
+			// call may be a write at this position (or a seek with SEEK_CUR)
+			if _, seekErr := f.base.Seek(int64(n), os.SEEK_CUR); seekErr != nil {
+				// only overwrite err in case the seek fails: we need to
+				// report an eventual io.EOF to the caller
+				err = seekErr
+			}
+		}
+		return n, err
+	}
+	if f.base != nil {
+		return f.base.Read(s)
+	}
+	return 0, syscall.EBADFD
+}
+
+func (f *UnionFile) ReadAt(s []byte, o int64) (int, error) {
+	if f.layer != nil {
+		n, err := f.layer.ReadAt(s, o)
+		if (err == nil || err == io.EOF) && f.base != nil {
+			_, err = f.base.Seek(o+int64(n), os.SEEK_SET)
+		}
+		return n, err
+	}
+	if f.base != nil {
+		return f.base.ReadAt(s, o)
+	}
+	return 0, syscall.EBADFD
+}
+
+func (f *UnionFile) Seek(o int64, w int) (pos int64, err error) {
+	if f.layer != nil {
+		pos, err = f.layer.Seek(o, w)
+		if (err == nil || err == io.EOF) && f.base != nil {
+			_, err = f.base.Seek(o, w)
+		}
+		return pos, err
+	}
+	if f.base != nil {
+		return f.base.Seek(o, w)
+	}
+	return 0, syscall.EBADFD
+}
+
+func (f *UnionFile) Write(s []byte) (n int, err error) {
+	if f.layer != nil {
+		n, err = f.layer.Write(s)
+		if err == nil && f.base != nil { // hmm, do we have fixed size files where a write may hit the EOF mark?
+			_, err = f.base.Write(s)
+		}
+		return n, err
+	}
+	if f.base != nil {
+		return f.base.Write(s)
+	}
+	return 0, syscall.EBADFD
+}
+
+func (f *UnionFile) WriteAt(s []byte, o int64) (n int, err error) {
+	if f.layer != nil {
+		n, err = f.layer.WriteAt(s, o)
+		if err == nil && f.base != nil {
+			_, err = f.base.WriteAt(s, o)
+		}
+		return n, err
+	}
+	if f.base != nil {
+		return f.base.WriteAt(s, o)
+	}
+	return 0, syscall.EBADFD
+}
+
+func (f *UnionFile) Name() string {
+	if f.layer != nil {
+		return f.layer.Name()
+	}
+	return f.base.Name()
+}
+
+func (f *UnionFile) Readdir(c int) (ofi []os.FileInfo, err error) {
+	if f.off == 0 {
+		var files = make(map[string]os.FileInfo)
+		var rfi []os.FileInfo
+		if f.layer != nil {
+			rfi, err = f.layer.Readdir(-1)
+			if err != nil {
+				return nil, err
+			}
+			for _, fi := range rfi {
+				files[fi.Name()] = fi
+			}
+		}
+		if f.base != nil {
+			rfi, err = f.base.Readdir(-1)
+			if err != nil {
+				return nil, err
+			}
+			for _, fi := range rfi {
+				if _, exists := files[fi.Name()]; !exists {
+					files[fi.Name()] = fi
+				}
+			}
+		}
+		for _, fi := range files {
+			f.files = append(f.files, fi)
+		}
+	}
+	if c == -1 {
+		return f.files[f.off:], nil
+	}
+	defer func() { f.off += c }()
+	return f.files[f.off:c], nil
+}
+
+func (f *UnionFile) Readdirnames(c int) ([]string, error) {
+	rfi, err := f.Readdir(c)
+	if err != nil {
+		return nil, err
+	}
+	var names []string
+	for _, fi := range rfi {
+		names = append(names, fi.Name())
+	}
+	return names, nil
+}
+
+func (f *UnionFile) Stat() (os.FileInfo, error) {
+	if f.layer != nil {
+		return f.layer.Stat()
+	}
+	if f.base != nil {
+		return f.base.Stat()
+	}
+	return nil, syscall.EBADFD
+}
+
+func (f *UnionFile) Sync() (err error) {
+	if f.layer != nil {
+		err = f.layer.Sync()
+		if err == nil && f.base != nil {
+			err = f.base.Sync()
+		}
+		return err
+	}
+	if f.base != nil {
+		return f.base.Sync()
+	}
+	return syscall.EBADFD
+}
+
+func (f *UnionFile) Truncate(s int64) (err error) {
+	if f.layer != nil {
+		err = f.layer.Truncate(s)
+		if err == nil && f.base != nil {
+			err = f.base.Truncate(s)
+		}
+		return err
+	}
+	if f.base != nil {
+		return f.base.Truncate(s)
+	}
+	return syscall.EBADFD
+}
+
+func (f *UnionFile) WriteString(s string) (n int, err error) {
+	if f.layer != nil {
+		n, err = f.layer.WriteString(s)
+		if err == nil && f.base != nil {
+			_, err = f.base.WriteString(s)
+		}
+		return n, err
+	}
+	if f.base != nil {
+		return f.base.WriteString(s)
+	}
+	return 0, syscall.EBADFD
+}
diff --git a/union_cache.go b/union_cache.go
new file mode 100644
index 0000000..bae895b
--- /dev/null
+++ b/union_cache.go
@@ -0,0 +1,307 @@
+package afero
+
+import (
+	"os"
+	"syscall"
+	"time"
+)
+
+// If the cache duration is 0, cache time will be unlimited, i.e. once
+// a file is in the layer, the base will never be read again for this file.
+//
+// For cache times greater than 0, the modification time of a file is
+// checked. Note that a lot of file system implementations only allow a
+// resolution of a second for timestamps... or as the godoc for os.Chtimes()
+// states: "The underlying filesystem may truncate or round the values to a
+// less precise time unit."
+//
+// This caching union will forward all write calls also to the base file
+// system first. To prevent writing to the base Fs, wrap it in a read-only
+// filter - Note: this will also make the overlay read-only, for writing files
+// in the overlay, use the overlay Fs directly, not via the union Fs.
+type CacheUnionFs struct {
+	base      Fs
+	layer     Fs
+	cacheTime time.Duration
+}
+
+func NewCacheUnionFs(t time.Duration) UnionFs {
+	return func(layer Fs) FilterFs {
+		return &CacheUnionFs{cacheTime: t, layer: layer}
+	}
+}
+
+func (u *CacheUnionFs) AddFilter(fs FilterFs) {
+	fs.SetSource(u.base)
+	u.base = fs
+}
+
+func (u *CacheUnionFs) SetSource(fs Fs) {
+	u.base = fs
+}
+
+type cacheState int
+
+const (
+	cacheUnknown cacheState = iota
+	// not present in the overlay, unknown if it exists in the base:
+	cacheMiss
+	// present in the overlay and in base, base file is newer:
+	cacheStale
+	// present in the overlay - with cache time == 0 it may exist in the base,
+	// with cacheTime > 0 it exists in the base and is same age or newer in the
+	// overlay
+	cacheHit
+	// happens if someone writes directly to the overlay without
+	// going through this union
+	cacheLocal
+)
+
+func (u *CacheUnionFs) cacheStatus(name string) (state cacheState, fi os.FileInfo, err error) {
+	var lfi, bfi os.FileInfo
+	lfi, err = u.layer.Stat(name)
+	if err == nil {
+		if u.cacheTime == 0 {
+			return cacheHit, lfi, nil
+		}
+		if lfi.ModTime().Add(u.cacheTime).Before(time.Now()) {
+			bfi, err = u.base.Stat(name)
+			if err != nil {
+				return cacheLocal, lfi, nil
+			}
+			if bfi.ModTime().After(lfi.ModTime()) {
+				return cacheStale, bfi, nil
+			}
+		}
+		return cacheHit, lfi, nil
+	}
+
+	if err == syscall.ENOENT {
+		return cacheMiss, nil, nil
+	}
+	var ok bool
+	if err, ok = err.(*os.PathError); ok {
+		if err == os.ErrNotExist {
+			return cacheMiss, nil, nil
+		}
+	}
+	return cacheMiss, nil, err
+}
+
+func (u *CacheUnionFs) copyToLayer(name string) error {
+	return copyToLayer(u.base, u.layer, name)
+}
+
+func (u *CacheUnionFs) Chtimes(name string, atime, mtime time.Time) error {
+	st, _, err := u.cacheStatus(name)
+	if err != nil {
+		return err
+	}
+	switch st {
+	case cacheLocal:
+	case cacheHit:
+		err = u.base.Chtimes(name, atime, mtime)
+	case cacheStale, cacheMiss:
+		if err := u.copyToLayer(name); err != nil {
+			return err
+		}
+		err = u.base.Chtimes(name, atime, mtime)
+	}
+	if err != nil {
+		return err
+	}
+	return u.layer.Chtimes(name, atime, mtime)
+}
+
+func (u *CacheUnionFs) Chmod(name string, mode os.FileMode) error {
+	st, _, err := u.cacheStatus(name)
+	if err != nil {
+		return err
+	}
+	switch st {
+	case cacheLocal:
+	case cacheHit:
+		err = u.base.Chmod(name, mode)
+	case cacheStale, cacheMiss:
+		if err := u.copyToLayer(name); err != nil {
+			return err
+		}
+		err = u.base.Chmod(name, mode)
+	}
+	if err != nil {
+		return err
+	}
+	return u.layer.Chmod(name, mode)
+}
+
+func (u *CacheUnionFs) Stat(name string) (os.FileInfo, error) {
+	st, fi, err := u.cacheStatus(name)
+	if err != nil {
+		return nil, err
+	}
+	switch st {
+	case cacheMiss:
+		return u.base.Stat(name)
+	default: // cacheStale has base, cacheHit and cacheLocal the layer os.FileInfo
+		return fi, nil
+	}
+}
+
+func (u *CacheUnionFs) Rename(oldname, newname string) error {
+	st, _, err := u.cacheStatus(oldname)
+	if err != nil {
+		return err
+	}
+	switch st {
+	case cacheLocal:
+	case cacheHit:
+		err = u.base.Rename(oldname, newname)
+	case cacheStale, cacheMiss:
+		if err := u.copyToLayer(oldname); err != nil {
+			return err
+		}
+		err = u.base.Rename(oldname, newname)
+	}
+	if err != nil {
+		return err
+	}
+	return u.layer.Rename(oldname, newname)
+}
+
+func (u *CacheUnionFs) Remove(name string) error {
+	st, _, err := u.cacheStatus(name)
+	if err != nil {
+		return err
+	}
+	switch st {
+	case cacheLocal:
+	case cacheHit, cacheStale, cacheMiss:
+		err = u.base.Remove(name)
+	}
+	if err != nil {
+		return err
+	}
+	return u.layer.Remove(name)
+}
+
+func (u *CacheUnionFs) RemoveAll(name string) error {
+	st, _, err := u.cacheStatus(name)
+	if err != nil {
+		return err
+	}
+	switch st {
+	case cacheLocal:
+	case cacheHit, cacheStale, cacheMiss:
+		err = u.base.RemoveAll(name)
+	}
+	if err != nil {
+		return err
+	}
+	return u.layer.RemoveAll(name)
+}
+
+func (u *CacheUnionFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
+	st, _, err := u.cacheStatus(name)
+	if err != nil {
+		return nil, err
+	}
+	switch st {
+	case cacheLocal, cacheHit:
+	default:
+		if err := u.copyToLayer(name); err != nil {
+			return nil, err
+		}
+	}
+	if flag&(os.O_WRONLY|syscall.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 {
+		bfi, err := u.base.OpenFile(name, flag, perm)
+		if err != nil {
+			return nil, err
+		}
+		lfi, err := u.layer.OpenFile(name, flag, perm)
+		if err != nil {
+			bfi.Close() // oops, what if O_TRUNC was set and file opening in the layer failed...?
+			return nil, err
+		}
+		return &UnionFile{base: bfi, layer: lfi}, nil
+	}
+	return u.layer.OpenFile(name, flag, perm)
+}
+
+func (u *CacheUnionFs) Open(name string) (File, error) {
+	st, fi, err := u.cacheStatus(name)
+	if err != nil {
+		return nil, err
+	}
+
+	switch st {
+	case cacheLocal:
+		return u.layer.Open(name)
+
+	case cacheMiss:
+		bfi, err := u.base.Stat(name)
+		if err != nil {
+			return nil, err
+		}
+		if bfi.IsDir() {
+			return u.base.Open(name)
+		}
+		if err := u.copyToLayer(name); err != nil {
+			return nil, err
+		}
+		return u.layer.Open(name)
+
+	case cacheStale:
+		if !fi.IsDir() {
+			if err := u.copyToLayer(name); err != nil {
+				return nil, err
+			}
+			return u.layer.Open(name)
+		}
+	case cacheHit:
+		if !fi.IsDir() {
+			return u.layer.Open(name)
+		}
+	}
+	// the dirs from cacheHit, cacheStale fall down here:
+	bfile, _ := u.base.Open(name)
+	lfile, err := u.layer.Open(name)
+	if err != nil && bfile == nil {
+		return nil, err
+	}
+	return &UnionFile{base: bfile, layer: lfile}, nil
+}
+
+func (u *CacheUnionFs) Mkdir(name string, perm os.FileMode) error {
+	err := u.base.Mkdir(name, perm)
+	if err != nil {
+		return err
+	}
+	return u.layer.MkdirAll(name, perm) // yes, MkdirAll... we cannot assume it exists in the cache
+}
+
+func (u *CacheUnionFs) Name() string {
+	return "CacheUnionFs"
+}
+
+func (u *CacheUnionFs) MkdirAll(name string, perm os.FileMode) error {
+	err := u.base.MkdirAll(name, perm)
+	if err != nil {
+		return err
+	}
+	return u.layer.MkdirAll(name, perm)
+}
+
+func (u *CacheUnionFs) Create(name string) (File, error) {
+	bfh, err := u.base.Create(name)
+	if err != nil {
+		return nil, err
+	}
+	lfh, err := u.layer.Create(name)
+	if err != nil {
+		// oops, see comment about OS_TRUNC above, should we remove? then we have to
+		// remember if the file did not exist before
+		bfh.Close()
+		return nil, err
+	}
+	return &UnionFile{base: bfh, layer: lfh}, nil
+}
diff --git a/union_cow.go b/union_cow.go
new file mode 100644
index 0000000..0c75e2b
--- /dev/null
+++ b/union_cow.go
@@ -0,0 +1,213 @@
+package afero
+
+import (
+	"os"
+	"syscall"
+	"time"
+)
+
+// The CoWUnionFs is a union filesystem: a read only base file system with
+// a possibly writeable layer on top. Changes to the file system will only
+// be made in the overlay: Changing an existing file in the base layer which
+// is not present in the overlay will copy the file to the overlay ("changing"
+// includes also calls to e.g. Chtimes() and Chmod()).
+// The overlay is currently limited to MemMapFs:
+//  - missing MkdirAll() calls in the code below, MemMapFs creates them
+//    implicitly (or better: records the full path and afero.Readdir()
+//    can handle this).
+//
+// Reading directories is currently only supported via Open(), not OpenFile().
+type CoWUnionFs struct {
+	base  Fs
+	layer Fs
+}
+
+func NewCoWUnionFs() UnionFs {
+	// returns a function to have it the same as other implemtations
+	return func(layer Fs) FilterFs {
+		return &CoWUnionFs{layer: layer}
+	}
+}
+
+func (u *CoWUnionFs) AddFilter(fs FilterFs) {
+	fs.SetSource(u.base)
+	u.base = fs
+}
+
+func (u *CoWUnionFs) SetSource(fs Fs) {
+	u.base = fs
+}
+
+func (u *CoWUnionFs) isBaseFile(name string) (bool, error) {
+	if _, err := u.layer.Stat(name); err == nil {
+		return false, nil
+	}
+	_, err := u.base.Stat(name)
+	return true, err
+}
+
+func (u *CoWUnionFs) copyToLayer(name string) error {
+	return copyToLayer(u.base, u.layer, name)
+}
+
+func (u *CoWUnionFs) Chtimes(name string, atime, mtime time.Time) error {
+	b, err := u.isBaseFile(name)
+	if err != nil {
+		return err
+	}
+	if b {
+		if err := u.copyToLayer(name); err != nil {
+			return err
+		}
+	}
+	return u.layer.Chtimes(name, atime, mtime)
+}
+
+func (u *CoWUnionFs) Chmod(name string, mode os.FileMode) error {
+	b, err := u.isBaseFile(name)
+	if err != nil {
+		return err
+	}
+	if b {
+		if err := u.copyToLayer(name); err != nil {
+			return err
+		}
+	}
+	return u.layer.Chmod(name, mode)
+}
+
+func (u *CoWUnionFs) Stat(name string) (os.FileInfo, error) {
+	fi, err := u.layer.Stat(name)
+	switch err {
+	case nil:
+		return fi, nil
+	case syscall.ENOENT:
+		return u.base.Stat(name)
+	default:
+		return nil, err
+	}
+}
+
+// Renaming files present only in the base layer is not permitted
+func (u *CoWUnionFs) Rename(oldname, newname string) error {
+	b, err := u.isBaseFile(oldname)
+	if err != nil {
+		return err
+	}
+	if b {
+		return syscall.EPERM
+	}
+	return u.layer.Rename(oldname, newname)
+}
+
+// Removing files present only in the base layer is not permitted. If
+// a file is present in the base layer and the overlay, only the overlay
+// will be removed.
+func (u *CoWUnionFs) Remove(name string) error {
+	err := u.layer.Remove(name)
+	switch err {
+	case syscall.ENOENT:
+		_, err = u.base.Stat(name)
+		if err == nil {
+			return syscall.EPERM
+		}
+		return syscall.ENOENT
+	default:
+		return err
+	}
+}
+
+func (u *CoWUnionFs) RemoveAll(name string) error {
+	err := u.layer.RemoveAll(name)
+	switch err {
+	case syscall.ENOENT:
+		_, err = u.base.Stat(name)
+		if err == nil {
+			return syscall.EPERM
+		}
+		return syscall.ENOENT
+	default:
+		return err
+	}
+}
+
+func (u *CoWUnionFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
+	b, err := u.isBaseFile(name)
+	if err != nil {
+		return nil, err
+	}
+
+	if flag&(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 {
+		if b {
+			if err = u.copyToLayer(name); err != nil {
+				return nil, err
+			}
+		}
+		return u.layer.OpenFile(name, flag, perm)
+	}
+	if b {
+		return u.base.OpenFile(name, flag, perm)
+	}
+	return u.layer.OpenFile(name, flag, perm)
+}
+
+func (u *CoWUnionFs) Open(name string) (File, error) {
+	b, err := u.isBaseFile(name)
+	if err != nil {
+		return nil, err
+	}
+	if b {
+		return u.base.Open(name)
+	}
+
+	dir, err := IsDir(u.layer, name)
+	if err != nil {
+		return nil, err
+	}
+	if !dir {
+		return u.layer.Open(name)
+	}
+
+	bfile, _ := u.base.Open(name)
+	lfile, err := u.layer.Open(name)
+	if err != nil && bfile == nil {
+		return nil, err
+	}
+	return &UnionFile{base: bfile, layer: lfile}, nil
+}
+
+func (u *CoWUnionFs) Mkdir(name string, perm os.FileMode) error {
+	dir, err := IsDir(u.base, name)
+	if err != nil {
+		return u.layer.MkdirAll(name, perm)
+	}
+	if dir {
+		return syscall.EEXIST
+	}
+	return u.layer.MkdirAll(name, perm)
+}
+
+func (u *CoWUnionFs) Name() string {
+	return "CoWUnionFs"
+}
+
+func (u *CoWUnionFs) MkdirAll(name string, perm os.FileMode) error {
+	dir, err := IsDir(u.base, name)
+	if err != nil {
+		return u.layer.MkdirAll(name, perm)
+	}
+	if dir {
+		return syscall.EEXIST
+	}
+	return u.layer.MkdirAll(name, perm)
+}
+
+func (u *CoWUnionFs) Create(name string) (File, error) {
+	b, err := u.isBaseFile(name)
+	if err == nil && b {
+		if err = u.copyToLayer(name); err != nil {
+			return nil, err
+		}
+	}
+	return u.layer.Create(name)
+}
diff --git a/union_test.go b/union_test.go
new file mode 100644
index 0000000..14736ff
--- /dev/null
+++ b/union_test.go
@@ -0,0 +1,143 @@
+package afero
+
+import (
+	"io/ioutil"
+	"os"
+	"testing"
+	"time"
+)
+
+func TestUnionCreateExisting(t *testing.T) {
+	base := &MemMapFs{}
+	roBase := NewFilter(base)
+	roBase.AddFilter(NewReadonlyFilter())
+
+	ufs := NewUnionFs(roBase, &MemMapFs{}, NewCoWUnionFs())
+
+	base.MkdirAll("/home/test", 0777)
+	fh, _ := base.Create("/home/test/file.txt")
+	fh.WriteString("This is a test")
+	fh.Close()
+
+	fh, err := ufs.OpenFile("/home/test/file.txt", os.O_RDWR, 0666)
+	if err != nil {
+		t.Errorf("Failed to open file r/w: %s", err)
+	}
+
+	_, err = fh.Write([]byte("####"))
+	if err != nil {
+		t.Errorf("Failed to write file: %s", err)
+	}
+	fh.Seek(0, 0)
+	data, err := ioutil.ReadAll(fh)
+	if err != nil {
+		t.Errorf("Failed to read file: %s", err)
+	}
+	if string(data) != "#### is a test" {
+		t.Errorf("Got wrong data")
+	}
+	fh.Close()
+
+	fh, _ = base.Open("/home/test/file.txt")
+	data, err = ioutil.ReadAll(fh)
+	if string(data) != "This is a test" {
+		t.Errorf("Got wrong data in base file")
+	}
+	fh.Close()
+
+	fh, err = ufs.Create("/home/test/file.txt")
+	switch err {
+	case nil:
+		if fi, _ := fh.Stat(); fi.Size() != 0 {
+			t.Errorf("Create did not truncate file")
+		}
+		fh.Close()
+	default:
+		t.Errorf("Create failed on existing file")
+	}
+
+}
+
+func TestUnionMergeReaddir(t *testing.T) {
+	base := &MemMapFs{}
+	roBase := NewFilter(base)
+	roBase.AddFilter(NewReadonlyFilter())
+
+	ufs := NewUnionFs(roBase, &MemMapFs{}, NewCoWUnionFs())
+
+	base.MkdirAll("/home/test", 0777)
+	fh, _ := base.Create("/home/test/file.txt")
+	fh.WriteString("This is a test")
+	fh.Close()
+
+	fh, _ = ufs.Create("/home/test/file2.txt")
+	fh.WriteString("This is a test")
+	fh.Close()
+
+	fh, _ = ufs.Open("/home/test")
+	files, err := fh.Readdirnames(-1)
+	if err != nil {
+		t.Errorf("Readdirnames failed")
+	}
+	if len(files) != 2 {
+		t.Errorf("Got wrong number of files: %v", files)
+	}
+}
+
+func TestUnionCacheWrite(t *testing.T) {
+	base := &MemMapFs{}
+	layer := &MemMapFs{}
+	ufs := NewUnionFs(base, layer, NewCacheUnionFs(0))
+
+	base.Mkdir("/data", 0777)
+
+	fh, err := ufs.Create("/data/file.txt")
+	if err != nil {
+		t.Errorf("Failed to create file")
+	}
+	_, err = fh.Write([]byte("This is a test"))
+	if err != nil {
+		t.Errorf("Failed to write file")
+	}
+
+	fh.Seek(0, os.SEEK_SET)
+	buf := make([]byte, 4)
+	_, err = fh.Read(buf)
+	fh.Write([]byte(" IS A"))
+	fh.Close()
+
+	baseData, _ := ReadFile(base, "/data/file.txt")
+	layerData, _ := ReadFile(layer, "/data/file.txt")
+	if string(baseData) != string(layerData) {
+		t.Errorf("Different data: %s <=> %s", baseData, layerData)
+	}
+}
+
+func TestUnionCacheExpire(t *testing.T) {
+	base := &MemMapFs{}
+	layer := &MemMapFs{}
+	ufs := NewUnionFs(base, layer, NewCacheUnionFs(1*time.Second))
+
+	base.Mkdir("/data", 0777)
+
+	fh, err := ufs.Create("/data/file.txt")
+	if err != nil {
+		t.Errorf("Failed to create file")
+	}
+	_, err = fh.Write([]byte("This is a test"))
+	if err != nil {
+		t.Errorf("Failed to write file")
+	}
+	fh.Close()
+
+	fh, _ = base.Create("/data/file.txt")
+	// sleep some time, so we really get a different time.Now() on write...
+	time.Sleep(2 * time.Second)
+	fh.WriteString("Another test")
+	fh.Close()
+
+	data, _ := ReadFile(ufs, "/data/file.txt")
+	if string(data) != "Another test" {
+		t.Errorf("cache time failed: <%s>", data)
+	}
+}