// nolint:goheader /* Copyright © 2022 spf13/afero Copyright © 2022 SUSE LLC 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 utils import ( "io/fs" "os" "path/filepath" "sort" "strconv" "strings" "sync" "syscall" "time" v1 "github.com/kairos-io/kairos/v2/pkg/types/v1" "github.com/twpayne/go-vfs" "github.com/twpayne/go-vfs/vfst" ) // DirSize returns the accumulated size of all files in folder func DirSize(fs v1.FS, path string) (int64, error) { var size int64 err := vfs.Walk(fs, path, func(_ string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { size += info.Size() } return err }) return size, err } // Check if a file or directory exists. func Exists(fs v1.FS, path string) (bool, error) { _, err := fs.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err } // IsDir check if the path is a dir func IsDir(fs v1.FS, path string) (bool, error) { fi, err := fs.Stat(path) if err != nil { return false, err } return fi.IsDir(), nil } // MkdirAll directory and all parents if not existing func MkdirAll(fs v1.FS, name string, mode os.FileMode) (err error) { if _, isReadOnly := fs.(*vfs.ReadOnlyFS); isReadOnly { return permError("mkdir", name) } if name, err = fs.RawPath(name); err != nil { return &os.PathError{Op: "mkdir", Path: name, Err: err} } return os.MkdirAll(name, mode) } // permError returns an *os.PathError with Err syscall.EPERM. func permError(op, path string) error { return &os.PathError{ Op: op, Path: path, Err: syscall.EPERM, } } // Random number state. // We generate random temporary file names so that there's a good // chance the file doesn't exist yet - keeps the number of tries in // TempFile to a minimum. var rand uint32 var randmu sync.Mutex func reseed() uint32 { return uint32(time.Now().UnixNano() + int64(os.Getpid())) } func nextRandom() string { randmu.Lock() r := rand if r == 0 { r = reseed() } r = r*1664525 + 1013904223 // constants from Numerical Recipes rand = r randmu.Unlock() return strconv.Itoa(int(1e9 + r%1e9))[1:] } // TempDir creates a temp dir in the virtual fs // Took from afero.FS code and adapted func TempDir(fs v1.FS, dir, prefix string) (name string, err error) { if dir == "" { dir = os.TempDir() } // This skips adding random stuff to the created temp dir so the temp dir created is predictable for testing if _, isTestFs := fs.(*vfst.TestFS); isTestFs { err = MkdirAll(fs, filepath.Join(dir, prefix), 0700) if err != nil { return "", err } name = filepath.Join(dir, prefix) return } nconflict := 0 for i := 0; i < 10000; i++ { try := filepath.Join(dir, prefix+nextRandom()) err = MkdirAll(fs, try, 0700) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() rand = reseed() randmu.Unlock() } continue } if err == nil { name = try } break } return } // TempFile creates a temp file in the virtual fs // Took from afero.FS code and adapted func TempFile(fs v1.FS, dir, pattern string) (f *os.File, err error) { if dir == "" { dir = os.TempDir() } var prefix, suffix string if pos := strings.LastIndex(pattern, "*"); pos != -1 { prefix, suffix = pattern[:pos], pattern[pos+1:] } else { prefix = pattern } nconflict := 0 for i := 0; i < 10000; i++ { name := filepath.Join(dir, prefix+nextRandom()+suffix) f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() rand = reseed() randmu.Unlock() } continue } break } return } // Walkdir with an FS implementation type statDirEntry struct { info fs.FileInfo } func (d *statDirEntry) Name() string { return d.info.Name() } func (d *statDirEntry) IsDir() bool { return d.info.IsDir() } func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() } func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil } // WalkDirFs is the same as filepath.WalkDir but accepts a v1.Fs so it can be run on any v1.Fs type func WalkDirFs(fs v1.FS, root string, fn fs.WalkDirFunc) error { info, err := fs.Stat(root) if err != nil { err = fn(root, nil, err) } else { err = walkDir(fs, root, &statDirEntry{info}, fn) } if err == filepath.SkipDir { return nil } return err } func walkDir(fs v1.FS, path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error { if err := walkDirFn(path, d, nil); err != nil || !d.IsDir() { if err == filepath.SkipDir && d.IsDir() { // Successfully skipped directory. err = nil } return err } dirs, err := readDir(fs, path) if err != nil { // Second call, to report ReadDir error. err = walkDirFn(path, d, err) if err != nil { return err } } for _, d1 := range dirs { path1 := filepath.Join(path, d1.Name()) if err := walkDir(fs, path1, d1, walkDirFn); err != nil { if err == filepath.SkipDir { break } return err } } return nil } func readDir(fs v1.FS, dirname string) ([]fs.DirEntry, error) { f, err := fs.Open(dirname) if err != nil { return nil, err } dirs, err := f.ReadDir(-1) f.Close() if err != nil { return nil, err } sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() }) return dirs, nil }