blob: d314e1641524400016fbf9a7b32a2de522a0101a [file] [log] [blame]
// Copyright 2024 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fileutils
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"dawn.googlesource.com/dawn/tools/src/oswrapper"
)
// CopyFile copies the file from 'src' to 'dst', creating the destination directory
// if needed and overwriting the destination file if it already exists.
// It preserves the file mode from the source.
func CopyFile(dst, src string, fsReaderWriter oswrapper.FilesystemReaderWriter) error {
srcInfo, err := fsReaderWriter.Stat(src)
if err != nil {
return fmt.Errorf("cannot stat source '%v': %w", src, err)
}
if srcInfo.IsDir() {
return fmt.Errorf("source '%v' is a directory, not a file", src)
}
s, err := fsReaderWriter.Open(src)
if err != nil {
return fmt.Errorf("failed to open source file '%v': %w", src, err)
}
defer s.Close()
if err := fsReaderWriter.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return fmt.Errorf("failed to create destination directory '%v': %w", filepath.Dir(dst), err)
}
d, err := fsReaderWriter.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, srcInfo.Mode())
if err != nil {
return fmt.Errorf("failed to open destination file '%v': %w", dst, err)
}
defer d.Close()
if _, err = io.Copy(d, s); err != nil {
return fmt.Errorf("failed to copy data from '%v' to '%v': %w", src, dst, err)
}
return nil
}
// CopyDir recursively copies the content of the 'src' directory to 'dst'.
// If 'dst' exists, it will be completely overwritten with the content of 'src'.
// If 'dst' does not exist, it will be created.
func CopyDir(dst, src string, fsReaderWriter oswrapper.FilesystemReaderWriter) error {
srcInfo, err := fsReaderWriter.Stat(src)
if err != nil {
return fmt.Errorf("cannot stat source '%v': %w", src, err)
}
if !srcInfo.IsDir() {
return fmt.Errorf("source '%v' is not a directory", src)
}
var dstMode = srcInfo.Mode()
if dstInfo, err := fsReaderWriter.Stat(dst); err == nil {
if !dstInfo.IsDir() {
return fmt.Errorf("destination '%v' is a file, not a directory", dst)
}
dstMode = dstInfo.Mode() // Preserve original dst permissions
} else if !os.IsNotExist(err) { // Some other error
return fmt.Errorf("cannot stat destination '%v': %w", dst, err)
}
// To prevent recursion, ensure dst is not a subdirectory of src.
cleanSrc := filepath.Clean(src)
cleanDst := filepath.Clean(dst)
if strings.HasPrefix(cleanDst, cleanSrc) && (cleanDst == cleanSrc || cleanDst[len(cleanSrc)] == filepath.Separator) {
return fmt.Errorf("cannot copy directory '%s' into itself '%s'", src, dst)
}
if err := fsReaderWriter.RemoveAll(dst); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove destination directory '%v': %w", dst, err)
}
if err := fsReaderWriter.MkdirAll(dst, dstMode); err != nil {
return fmt.Errorf("failed to create destination directory '%v': %w", dst, err)
}
return fsReaderWriter.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(src, path)
if err != nil {
return fmt.Errorf("failed to calculate relative path for '%s': %w", path, err)
}
if relPath == "." { // Skip the root directory itself.
return nil
}
dstPath := filepath.Join(dst, relPath)
if info.IsDir() {
return fsReaderWriter.MkdirAll(dstPath, info.Mode())
}
return CopyFile(dstPath, path, fsReaderWriter)
})
}