blob: 356683c51b079c5f5ee76804250f30198bfd535a [file] [log] [blame]
// Copyright 2018 The Chromium OS 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 fsutil implements common file operations.
package fsutil
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"chromiumos/tast/errors"
)
// CopyFile copies the regular file at path src to dst.
// dst is atomically replaced if it already exists and inherits src's mode.
// Ownership will also be preserved if the EUID is 0.
func CopyFile(src, dst string) error {
sf, err := os.Open(src)
if err != nil {
return errors.Wrap(err, "failed to open src file")
}
defer sf.Close()
fi, err := sf.Stat()
if err != nil {
return errors.Wrap(err, "failed to stat src file")
} else if !fi.Mode().IsRegular() {
return fmt.Errorf("source not regular file (mode %s)", fi.Mode())
}
// Copy to a temp file.
df, err := ioutil.TempFile(filepath.Dir(dst), "."+filepath.Base(dst)+".")
if err != nil {
return errors.Wrap(err, "failed to create tmp file")
}
if _, err := io.Copy(df, sf); err != nil {
df.Close()
os.Remove(df.Name())
return errors.Wrap(err, "failed to copy data from src file to tmp file")
}
if err := df.Close(); err != nil {
os.Remove(df.Name())
return errors.Wrap(err, "failed to close tmp file")
}
// Finally, set the mode and ownership and rename to the requested name.
if err := os.Chmod(df.Name(), fi.Mode()); err != nil {
os.Remove(df.Name())
return errors.Wrap(err, "failed to change permissions of tmp file")
}
if os.Geteuid() == 0 {
st := fi.Sys().(*syscall.Stat_t)
if err := os.Chown(df.Name(), int(st.Uid), int(st.Gid)); err != nil {
os.Remove(df.Name())
return errors.Wrap(err, "failed to change owner of tmp file")
}
}
if err := os.Rename(df.Name(), dst); err != nil {
os.Remove(df.Name())
return errors.Wrap(err, "failed to rename tmp file to dst file")
}
return nil
}
// MoveFile moves the file at src to dst.
// The source and destination paths may be on different filesystems.
// The mode will be preserved, and ownership will also be preserved if possible
// (i.e. if called with an EUID of 0 or if moving the file within a filesystem).
func MoveFile(src, dst string) error {
fi, err := os.Stat(src)
if err != nil {
return errors.Wrap(err, "failed to open src file")
} else if !fi.Mode().IsRegular() {
return fmt.Errorf("source not regular file (mode %s)", fi.Mode())
}
// Try to do a rename first. This only works if we're not moving across filesystems.
if err := os.Rename(src, dst); err == nil {
return nil
} else if lerr, ok := err.(*os.LinkError); !ok || lerr.Err != syscall.EXDEV {
return errors.Wrap(err, "failed to rename src to dst file")
}
// Otherwise, copy before deleting src.
if err := CopyFile(src, dst); err != nil {
return errors.Wrap(err, "failed to copy src to dst file")
}
return os.Remove(src)
}