/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package filesystem import ( "fmt" "os" "path/filepath" "runtime" "strings" "time" ) // DefaultFs implements Filesystem using same-named functions from "os" and "io" type DefaultFs struct { root string } var _ Filesystem = &DefaultFs{} // NewTempFs returns a fake Filesystem in temporary directory, useful for unit tests func NewTempFs() Filesystem { path, _ := os.MkdirTemp("", "tmpfs") return &DefaultFs{ root: path, } } func (fs *DefaultFs) prefix(path string) string { if len(fs.root) == 0 { return path } return filepath.Join(fs.root, path) } // Stat via os.Stat func (fs *DefaultFs) Stat(name string) (os.FileInfo, error) { return os.Stat(fs.prefix(name)) } // Create via os.Create func (fs *DefaultFs) Create(name string) (File, error) { file, err := os.Create(fs.prefix(name)) if err != nil { return nil, err } return &defaultFile{file}, nil } // Rename via os.Rename func (fs *DefaultFs) Rename(oldpath, newpath string) error { if !strings.HasPrefix(oldpath, fs.root) { oldpath = fs.prefix(oldpath) } if !strings.HasPrefix(newpath, fs.root) { newpath = fs.prefix(newpath) } return os.Rename(oldpath, newpath) } func (fs *DefaultFs) MkdirAll(path string, perm os.FileMode) error { return MkdirAll(fs.prefix(path), perm) } // MkdirAllWithPathCheck checks if path exists already. If not, it creates a directory // named path, along with any necessary parents, and returns nil, or else returns an error. // Permission bits perm (before umask) are used for all directories that // MkdirAllWithPathCheck creates. // If path is already a directory, MkdirAllWithPathCheck does nothing and returns nil. // NOTE: In case of Windows NTFS, mount points are implemented as reparse-point // (similar to symlink) and do not represent actual directory. Hence Directory existence // check for windows NTFS will NOT check for dir, but for symlink presence. func MkdirAllWithPathCheck(path string, perm os.FileMode) error { if dir, err := os.Lstat(path); err == nil { // If the path exists already, // 1. for Unix/Linux OS, check if the path is directory. // 2. for windows NTFS, check if the path is symlink instead of directory. if dir.IsDir() || (runtime.GOOS == "windows" && (dir.Mode()&os.ModeSymlink != 0)) { return nil } return fmt.Errorf("path %v exists but is not a directory", path) } // If existence of path not known, attempt to create it. if err := MkdirAll(path, perm); err != nil { return err } return nil } // Chtimes via os.Chtimes func (fs *DefaultFs) Chtimes(name string, atime time.Time, mtime time.Time) error { return os.Chtimes(fs.prefix(name), atime, mtime) } // RemoveAll via os.RemoveAll func (fs *DefaultFs) RemoveAll(path string) error { return os.RemoveAll(fs.prefix(path)) } // Remove via os.Remove func (fs *DefaultFs) Remove(name string) error { return os.Remove(fs.prefix(name)) } // ReadFile via os.ReadFile func (fs *DefaultFs) ReadFile(filename string) ([]byte, error) { return os.ReadFile(fs.prefix(filename)) } // TempDir via os.MkdirTemp func (fs *DefaultFs) TempDir(dir, prefix string) (string, error) { return os.MkdirTemp(fs.prefix(dir), prefix) } // TempFile via os.CreateTemp func (fs *DefaultFs) TempFile(dir, prefix string) (File, error) { file, err := os.CreateTemp(fs.prefix(dir), prefix) if err != nil { return nil, err } return &defaultFile{file}, nil } // ReadDir via os.ReadDir func (fs *DefaultFs) ReadDir(dirname string) ([]os.DirEntry, error) { return os.ReadDir(fs.prefix(dirname)) } // Walk via filepath.Walk func (fs *DefaultFs) Walk(root string, walkFn filepath.WalkFunc) error { return filepath.Walk(fs.prefix(root), walkFn) } // defaultFile implements File using same-named functions from "os" type defaultFile struct { file *os.File } // Name via os.File.Name func (file *defaultFile) Name() string { return file.file.Name() } // Write via os.File.Write func (file *defaultFile) Write(b []byte) (n int, err error) { return file.file.Write(b) } // Sync via os.File.Sync func (file *defaultFile) Sync() error { return file.file.Sync() } // Close via os.File.Close func (file *defaultFile) Close() error { return file.file.Close() }