mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -06:00
b287047052
* WIP * Set public_suffix to a pre Ruby 2.6 version * we don't need to install python * Stretch->Buster * Bump versions in lib.star * Manually update linter Sort of messy, but the .mod-file need to contain all dependencies that use 1.16+ features, otherwise they're assumed to be compiled with -lang=go1.16 and cannot access generics et al. Bingo doesn't seem to understand that, but it's possible to manually update things to get Bingo happy. * undo reformatting * Various lint improvements * More from the linter * goimports -w ./pkg/ * Disable gocritic * Add/modify linter exceptions * lint + flatten nested list Go 1.19 doesn't support nested lists, and there wasn't an obvious workaround. https://go.dev/doc/comment#lists
170 lines
3.6 KiB
Go
170 lines
3.6 KiB
Go
package fs
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// CopyFile copies a file from src to dst.
|
|
//
|
|
// If src and dst files exist, and are the same, then return success. Otherwise, attempt to create a hard link
|
|
// between the two files. If that fails, copy the file contents from src to dst.
|
|
func CopyFile(src, dst string) (err error) {
|
|
absSrc, err := filepath.Abs(src)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get absolute path of source file %q: %w", src, err)
|
|
}
|
|
sfi, err := os.Stat(src)
|
|
if err != nil {
|
|
err = fmt.Errorf("couldn't stat source file %q: %w", absSrc, err)
|
|
return
|
|
}
|
|
if !sfi.Mode().IsRegular() {
|
|
// Cannot copy non-regular files (e.g., directories, symlinks, devices, etc.)
|
|
return fmt.Errorf("non-regular source file %s (%q)", absSrc, sfi.Mode().String())
|
|
}
|
|
dpath := filepath.Dir(dst)
|
|
exists, err := Exists(dpath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !exists {
|
|
err = fmt.Errorf("destination directory doesn't exist: %q", dpath)
|
|
return
|
|
}
|
|
|
|
var dfi os.FileInfo
|
|
dfi, err = os.Stat(dst)
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return
|
|
}
|
|
} else {
|
|
if !(dfi.Mode().IsRegular()) {
|
|
return fmt.Errorf("non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
|
|
}
|
|
if os.SameFile(sfi, dfi) {
|
|
return copyPermissions(sfi.Name(), dfi.Name())
|
|
}
|
|
}
|
|
|
|
if err = os.Link(src, dst); err == nil {
|
|
return copyPermissions(src, dst)
|
|
}
|
|
|
|
err = copyFileContents(src, dst)
|
|
return err
|
|
}
|
|
|
|
// copyFileContents copies the contents of the file named src to the file named
|
|
// by dst. The file will be created if it does not already exist. If the
|
|
// destination file exists, all it's contents will be replaced by the contents
|
|
// of the source file.
|
|
func copyFileContents(src, dst string) (err error) {
|
|
// Can ignore gosec G304 here, since it's a general file copying function
|
|
// nolint:gosec
|
|
in, err := os.Open(src)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
if e := in.Close(); err == nil && e != nil {
|
|
err = e
|
|
}
|
|
}()
|
|
|
|
//nolint:gosec
|
|
out, err := os.Create(dst)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
if cerr := out.Close(); cerr != nil && err == nil {
|
|
err = cerr
|
|
}
|
|
}()
|
|
|
|
if _, err = io.Copy(out, in); err != nil {
|
|
return
|
|
}
|
|
|
|
if err := out.Sync(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return copyPermissions(src, dst)
|
|
}
|
|
|
|
func copyPermissions(src, dst string) error {
|
|
sfi, err := os.Lstat(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.Chmod(dst, sfi.Mode()); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CopyRecursive copies files and directories recursively.
|
|
func CopyRecursive(src, dst string) error {
|
|
sfi, err := os.Stat(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !sfi.IsDir() {
|
|
return CopyFile(src, dst)
|
|
}
|
|
|
|
if _, err := os.Stat(dst); os.IsNotExist(err) {
|
|
if err := os.MkdirAll(dst, sfi.Mode()); err != nil {
|
|
return fmt.Errorf("failed to create directory %q: %s", dst, err)
|
|
}
|
|
}
|
|
|
|
entries, err := os.ReadDir(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, entry := range entries {
|
|
srcPath := filepath.Join(src, entry.Name())
|
|
dstPath := filepath.Join(dst, entry.Name())
|
|
|
|
srcFi, err := os.Stat(srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch srcFi.Mode() & os.ModeType {
|
|
case os.ModeDir:
|
|
if err := CopyRecursive(srcPath, dstPath); err != nil {
|
|
return err
|
|
}
|
|
case os.ModeSymlink:
|
|
link, err := os.Readlink(srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.Symlink(link, dstPath); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
if err := CopyFile(srcPath, dstPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if srcFi.Mode()&os.ModeSymlink != 0 {
|
|
if err := os.Chmod(dstPath, srcFi.Mode()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|