pkg/fileutil/fileutil.go (102 lines of code) (raw):
// Copyright 2022 Google 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 fileutil contains utilities for filesystem operations.
package fileutil
import (
"bytes"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
)
type action string
const (
move action = "move"
copy action = "copy"
)
// AllPaths indicates all paths should be recursively walked for functions
// that walk the filesystem.
var AllPaths = func(path string, d fs.DirEntry) (bool, error) {
return true, nil
}
// MaybeCopyPathContents recursively copies the contents of srcPath to destPath.
func MaybeCopyPathContents(destPath, srcPath string, copyCondition func(path string, d fs.DirEntry) (bool, error)) error {
return moveOrCopyPath(copy, destPath, srcPath, copyCondition)
}
// MaybeMovePathContents moves the contents of srcPath to destPath.
func MaybeMovePathContents(destPath, srcPath string, moveCondition func(path string, d fs.DirEntry) (bool, error)) error {
return moveOrCopyPath(move, destPath, srcPath, moveCondition)
}
// moveOrCopyPath recursively copies or moves files and directories: from srcPath to destPath.
func moveOrCopyPath(moveOrCopy action, destPath, srcPath string, condition func(path string, d fs.DirEntry) (bool, error)) error {
return filepath.WalkDir(srcPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// Skip the root
if path == srcPath {
return nil
}
shouldCopy, err := condition(path, d)
if err != nil {
return err
}
if !shouldCopy {
if d.IsDir() {
return filepath.SkipDir
}
return nil
}
relPath, err := filepath.Rel(srcPath, path)
if err != nil {
return err
}
dest := filepath.Join(destPath, relPath)
if moveOrCopy == move {
if err := os.Rename(path, dest); err != nil {
return err
}
// Rename moves the entire directory, so don't need to continue
// walking the directory.
if d.IsDir() {
return filepath.SkipDir
}
return nil
}
if d.IsDir() {
return os.MkdirAll(dest, 0744)
}
return CopyFile(dest, path)
})
}
// CopyFile copies a file from src to dest
func CopyFile(dest, src string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := os.Create(dest)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
return err
}
// EnsureUnixLineEndings replaces windows style CRLF line endings with unix LF line endings. This is
// necessary for executable scripts with shebang as the "\r" gets seen as part of the shebang
// target, which doesn't exist.
func EnsureUnixLineEndings(file ...string) error {
isWriteable, err := IsWritable(file...)
if err != nil {
return err
}
if !isWriteable {
return nil
}
path := filepath.Join(file...)
data, err := os.ReadFile(path)
if err != nil {
return err
}
data = bytes.ReplaceAll(data, []byte{'\r', '\n'}, []byte{'\n'})
if err := os.WriteFile(path, data, os.FileMode(0755)); err != nil {
return err
}
return nil
}
// IsWritable returns true if the file at the path constructed by joining elem is writable by the owner.
func IsWritable(elem ...string) (bool, error) {
path := filepath.Join(elem...)
info, err := os.Stat(path)
if err != nil {
return false, fmt.Errorf("stat %q: %v", path, err)
}
// check that that user writable permission bit is set
return info.Mode().Perm()&0200 != 0, nil
}