blob: 85919d9cae4fed1fe9b14042b9d9eb341ad8704d [file] [log] [blame]
// Copyright 2021 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 fileutil
import (
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
)
// FileExists returns true if a file or directory exists at the
// specified path, otherwise it returns false. If an error is
// encountered while checking, an error is returned.
func FileExists(path string) (bool, error) {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return false, nil
} else if err != nil {
return false, err
}
return true, nil
}
// CopyFile copies a file at path src to dst. sfinfo
// is the os.FileInfo associated with the file at path src
// and must be derived from it. sfinfo may be nil, in which
// case the file at src is queried for its os.FileInfo,
// and symbolic links are followed.
//
// In effect, sfinfo is just an optimization to avoid
// querying the path for the os.FileInfo more than necessary.
//
// Thus, CopyFile always copies the bytes of the file at
// src to a new file created at dst with the same file mode
// as the old one.
//
// If srcFS != nil, then src is assumed to be a path within
// srcFS.
//
// Returns a non-nil error if copying or acquiring the
// os.FileInfo for the file fails.
func CopyFile(dst, src string, sfinfo fs.FileInfo, srcFS fs.FS) error {
var sf fs.File
var err error
if srcFS != nil {
sf, err = srcFS.Open(src)
} else {
sf, err = os.Open(src)
}
if err != nil {
return err
}
defer sf.Close()
if sfinfo == nil || sfinfo.Mode()&os.ModeSymlink != 0 {
sfinfo, err = sf.Stat()
if err != nil {
return err
}
}
df, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, sfinfo.Mode())
if err != nil {
return err
}
defer df.Close()
_, err = io.Copy(df, sf)
return err
}
// CopyDir recursively copies the directory at path src to
// a new directory at path dst. If a symlink is encountered
// along the way, its link is copied verbatim and installed
// in the destination directory hierarchy, as in CopySymlink.
//
// If srcFS != nil, then src is assumed to be a path within
// srcFS.
//
// dst and directories under dst may not retain the permissions
// of src or the corresponding directories under src. Instead,
// we always set the permissions of the new directories to 0755.
func CopyDir(dst, src string, srcFS fs.FS) error {
// Ignore the permissions of src, since if dst
// isn't writable we can't actually copy files into it.
// Pick a safe default that allows us to modify the
// directory and files within however we want, but let
// others only inspect it.
if err := os.MkdirAll(dst, 0755); err != nil {
return err
}
var des []fs.DirEntry
var err error
if srcFS != nil {
des, err = fs.ReadDir(srcFS, src)
} else {
des, err = os.ReadDir(src)
}
if err != nil {
return err
}
for _, de := range des {
fi, err := de.Info()
if err != nil {
return err
}
d, s := filepath.Join(dst, fi.Name()), filepath.Join(src, fi.Name())
if fi.IsDir() {
if err := CopyDir(d, s, srcFS); err != nil {
return err
}
} else if fi.Mode()&os.ModeSymlink != 0 {
return fmt.Errorf("symbolic links not supported")
} else {
if err := CopyFile(d, s, fi, srcFS); err != nil {
return err
}
}
}
return nil
}