mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-04-28 03:32:27 +00:00
250 lines
5.7 KiB
Go
250 lines
5.7 KiB
Go
// 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-agent/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
|
|
}
|