diff --git a/fileutils.go b/fileutils.go index 7c851d7..b60cb90 100644 --- a/fileutils.go +++ b/fileutils.go @@ -93,31 +93,53 @@ func CopyDirectory(source string, dest string) error { if err != nil { return err } - if err := os.MkdirAll(dest, fi.Mode()); err != nil { + + // Get owner. + st, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return fmt.Errorf("could not convert to syscall.Stat_t") + } + + // We have to pick an owner here anyway. + if err := MkdirAllNewAs(dest, fi.Mode(), int(st.Uid), int(st.Gid)); err != nil { return err } + return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { if err != nil { return err } - // get the relative path + // Get the relative path relPath, err := filepath.Rel(source, path) if err != nil { return nil } - // skip the source directory if info.IsDir() { + // Skip the source directory. if path != source { + // Get the owner. + st, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return fmt.Errorf("could not convert to syscall.Stat_t") + } + + uid := int(st.Uid) + gid := int(st.Gid) + if err := os.Mkdir(filepath.Join(dest, relPath), info.Mode()); err != nil { return err } + + if err := os.Lchown(filepath.Join(dest, relPath), uid, gid); err != nil { + return err + } } return nil } - // Copy the file + // Copy the file. if err := CopyFile(path, filepath.Join(dest, relPath)); err != nil { return err } diff --git a/idtools.go b/idtools.go new file mode 100644 index 0000000..161aec8 --- /dev/null +++ b/idtools.go @@ -0,0 +1,49 @@ +package fileutils + +import ( + "os" + "path/filepath" +) + +// MkdirAllNewAs creates a directory (include any along the path) and then modifies +// ownership ONLY of newly created directories to the requested uid/gid. If the +// directories along the path exist, no change of ownership will be performed +func MkdirAllNewAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { + // make an array containing the original path asked for, plus (for mkAll == true) + // all path components leading up to the complete path that don't exist before we MkdirAll + // so that we can chown all of them properly at the end. If chownExisting is false, we won't + // chown the full directory path if it exists + var paths []string + if _, err := os.Stat(path); err != nil && os.IsNotExist(err) { + paths = []string{path} + } else if err == nil { + // nothing to do; directory path fully exists already + return nil + } + + // walk back to "/" looking for directories which do not exist + // and add them to the paths array for chown after creation + dirPath := path + for { + dirPath = filepath.Dir(dirPath) + if dirPath == "/" { + break + } + if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) { + paths = append(paths, dirPath) + } + } + + if err := os.MkdirAll(path, mode); err != nil && !os.IsExist(err) { + return err + } + + // even if it existed, we will chown the requested path + any subpaths that + // didn't exist when we called MkdirAll + for _, pathComponent := range paths { + if err := os.Chown(pathComponent, ownerUID, ownerGID); err != nil { + return err + } + } + return nil +}