blob: 139a6faf4a9b6b7f96ec4a9099881a63e6f78973 [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package unionfs allows multiple file systems to be read as a union.
package unionfs
import (
"io/fs"
)
var _ fs.ReadDirFS = FS{}
// A FS is an FS presenting the union of the file systems in the slice. If
// multiple file systems provide a particular file, Open uses the FS listed
// earlier in the slice.
type FS []fs.FS
// Sub returns an FS corresponding to the merged subtree rooted at a set of
// fsys's dirs.
func Sub(fsys fs.FS, dirs ...string) (FS, error) {
var subs FS
for _, dir := range dirs {
if _, err := fs.Stat(fsys, dir); err != nil {
return nil, err
}
sub, err := fs.Sub(fsys, dir)
if err != nil {
return nil, err
}
subs = append(subs, sub)
}
return subs, nil
}
func (fsys FS) Open(name string) (fs.File, error) {
var errOut error
for _, sub := range fsys {
f, err := sub.Open(name)
if err == nil {
return f, nil
}
if errOut == nil {
errOut = err
}
}
return nil, errOut
}
func (fsys FS) ReadDir(name string) ([]fs.DirEntry, error) {
var all []fs.DirEntry
var seen map[string]bool // seen[name] is true if name is listed in all; lazily initialized
var errOut error
for _, sub := range fsys {
list, err := fs.ReadDir(sub, name)
if err != nil {
errOut = err
}
if len(all) == 0 {
all = append(all, list...)
} else {
if seen == nil {
// Initialize seen only after we get two different directory listings.
seen = make(map[string]bool)
for _, d := range all {
seen[d.Name()] = true
}
}
for _, d := range list {
name := d.Name()
if !seen[name] {
seen[name] = true
all = append(all, d)
}
}
}
}
if len(all) > 0 {
return all, nil
}
return nil, errOut
}