func (c *Copier) mkdirAll(dst string) error {
	if dst == "" {
		return errors.New("empty dst directory")
	}
	abs, err := filepath.Abs(filepath.Clean(dst))
	if err != nil {
		return fmt.Errorf("failed to get absolute path of %s: %s", dst, err)
	}
	split := strings.Split(abs, "/")
	split[0] = "/"
	var prevDir string
	for _, dir := range split[:len(split)-1] {
		currDir := filepath.Join(prevDir, dir)
		if _, err := os.Lstat(currDir); err != nil {
			if !os.IsNotExist(err) {
				return fmt.Errorf("stat %s: %s", currDir, err)
			}
			if err := os.Mkdir(currDir, 0755); err != nil {
				return fmt.Errorf("mkdir %s with default mode 0755: %s", currDir, err)
			}
			if err := os.Chown(currDir, 0, 0); err != nil {
				return fmt.Errorf("chown %s with default owner (0:0): %s", currDir, err)
			}
		}
		prevDir = currDir
	}
	if _, err := os.Lstat(abs); err != nil {
		if !os.IsNotExist(err) {
			return fmt.Errorf("stat %s: %s", abs, err)
		}
		if err := os.Mkdir(abs, 0755); err != nil {
			return fmt.Errorf("mkdir %s with default mode 0755: %s", abs, err)
		}
		if c.dstDirOwner != nil {
			if err := os.Chown(abs, c.dstDirOwner.uid, c.dstDirOwner.gid); err != nil {
				return fmt.Errorf(
					"chown %s with owner (%d:%d): %s", abs, c.dstDirOwner.uid, c.dstDirOwner.gid, err)
			}
		} else {
			if err := os.Chown(abs, 0, 0); err != nil {
				return fmt.Errorf("chown %s with default owner (0:0): %s", abs, err)
			}
		}
	} else {
		if c.dstDirOwner != nil && c.dstDirOwner.overwrite {
			if err := os.Chown(abs, c.dstDirOwner.uid, c.dstDirOwner.gid); err != nil {
				return fmt.Errorf(
					"chown %s with owner (%d:%d): %s", abs, c.dstDirOwner.uid, c.dstDirOwner.gid, err)
			}
		}
	}
	return nil
}
mkdirAll performs the same operation as os.MkdirAll, except it also changes
permission and owner of dst directory.
- Parent directories will have 0755 permission and owner by root.
- Target directory's permission and owner will be decided by copier
attributes.
package fileio
import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"github.com/uber/makisu/lib/log"
	"github.com/uber/makisu/lib/pathutils"
	"github.com/uber/makisu/lib/utils"
)
type Copier struct {
	blacklist []string
	dstDirOwner *Owner
	dstFileAndChildrenOwner *Owner
}
type Owner struct {
	uid       int
	gid       int
	overwrite bool
}
func NewCopier(blacklist []string, opts ...CopyOption) *Copier {
	c := &Copier{
		blacklist: append(blacklist),
	}
	for _, opt := range opts {
		opt(c)
	}
	return c
}
type CopyOption func(*Copier)
func WithDstDirOwner(uid, gid int, overwrite bool) CopyOption {
	return func(c *Copier) {
		c.dstDirOwner = &Owner{
			uid:       uid,
			gid:       gid,
			overwrite: overwrite,
		}
	}
}
func WithDstFileAndChildrenOwner(uid, gid int, overwrite bool) CopyOption {
	return func(c *Copier) {
		c.dstFileAndChildrenOwner = &Owner{
			uid:       uid,
			gid:       gid,
			overwrite: overwrite,
		}
	}
}
func (c *Copier) CopyFile(source, target string) error {
	targetDir := filepath.Dir(target)
	if err := c.mkdirAll(targetDir); err != nil {
		return fmt.Errorf("mkdir all %s: %s", targetDir, err)
	}
	return c.copyFile(source, target)
}
func (c *Copier) CopyDir(source, target string) error {
	if c.isBlacklisted(source) {
		log.Infof("* Ignoring copy of directory %s because it is blacklisted", source)
		return nil
	}
	if err := c.mkdirAll(target); err != nil {
		return fmt.Errorf("mkdir all %s: %s", target, err)
	}
	return c.copyDirContents(source, target, target)
}
func (c *Copier) isBlacklisted(source string) bool {
	return pathutils.IsDescendantOfAny(source, c.blacklist)
}
func (c *Copier) copyFile(src, dst string) error {
	fi, err := os.Lstat(src)
	if err != nil {
		return fmt.Errorf("lstat %s: %s", src, err)
	} else if c.isBlacklisted(src) {
		log.Infof("* Ignoring copy of file %s because it is blacklisted", src)
	} else if utils.IsSpecialFile(fi) {
		return nil
	}
	if fi.Mode()&os.ModeSymlink != 0 {
		return c.copySymlink(src, dst)
	}
	if _, err := os.Lstat(dst); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("lstat %s: %s", dst, err)
	} else if err == nil {
		if err := os.Chmod(dst, os.ModePerm); err != nil {
			return fmt.Errorf("chmod %s: %s", dst, err)
		}
	}
	return c.copyRegularFile(fi, src, dst)
}
func (c *Copier) copyRegularFile(fi os.FileInfo, src, dst string) error {
	r, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("open %s: %s", dst, err)
	}
	defer r.Close()
	w, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, os.ModePerm)
	if err != nil {
		return fmt.Errorf("create %s: %s", dst, err)
	}
	defer w.Close()
	if err := os.Truncate(dst, 0); err != nil {
		return fmt.Errorf("truncate %s: %s", dst, err)
	}
	if _, err := io.Copy(w, r); err != nil {
		return fmt.Errorf("copy %s to %s: %s", src, dst, err)
	}
	uid, gid := getFileOwners(fi)
	if c.dstFileAndChildrenOwner != nil && c.dstFileAndChildrenOwner.overwrite {
		uid = c.dstFileAndChildrenOwner.uid
		gid = c.dstFileAndChildrenOwner.gid
	}
	if err := os.Chown(dst, uid, gid); err != nil {
		return fmt.Errorf("chown %s: %s", dst, err)
	}
	if err := os.Chmod(dst, fi.Mode()); err != nil {
		return fmt.Errorf("chmod %s: %s", dst, err)
	}
	return nil
}
func (c *Copier) copySymlink(src, dst string) error {
	if _, err := os.Lstat(dst); err == nil {
		if err := os.Remove(dst); err != nil {
			return fmt.Errorf("remove existing file %s: %s", dst, err)
		}
	}
	linkTarget, err := os.Readlink(src)
	if err != nil {
		return fmt.Errorf("read link %s: %s", src, err)
	}
	if err := os.Symlink(linkTarget, dst); err != nil {
		return fmt.Errorf("write link %s with content %s: %s", dst, linkTarget, err)
	}
	return nil
}
func (c *Copier) copyDirContents(src, dst, origDst string) error {
	entries, err := ioutil.ReadDir(src)
	if err != nil {
		return fmt.Errorf("read dir %s: %s", src, err)
	}
	for _, entry := range entries {
		currSrc := filepath.Join(src, entry.Name())
		if c.isBlacklisted(currSrc) {
			log.Infof("* Ignoring copy of directory %s because it is blacklisted", currSrc)
			continue
		} else if currSrc == origDst {
			continue
		}
		currDst := filepath.Join(dst, entry.Name())
		if entry.IsDir() {
			if err := c.copyDir(currSrc, currDst); err != nil {
				return fmt.Errorf("copy dir %s to %s: %s", currSrc, currDst, err)
			}
			if err := c.copyDirContents(currSrc, currDst, origDst); err != nil {
				return fmt.Errorf("copy dir contents %s to %s: %s", currSrc, currDst, err)
			}
		} else {
			if err := c.copyFile(currSrc, currDst); err != nil {
				return fmt.Errorf("copy file %s to %s: %s", currSrc, currDst, err)
			}
		}
	}
	return nil
}
func (c *Copier) copyDir(src, dst string) error {
	srcInfo, err := os.Lstat(src)
	if err != nil {
		return fmt.Errorf("lstat %s: %s", src, err)
	} else if !srcInfo.IsDir() {
		return fmt.Errorf("source %s is not a directory", src)
	} else if c.isBlacklisted(src) {
		log.Infof("* Ignoring copy of directory %s because it is blacklisted", src)
		return nil
	}
	dstInfo, err := os.Lstat(dst)
	if os.IsNotExist(err) {
		if err := os.Mkdir(dst, srcInfo.Mode()); err != nil {
			return fmt.Errorf("mkdir %s: %s", dst, err)
		}
	} else if err != nil {
		return fmt.Errorf("lstat %s: %s", dst, err)
	} else if err == nil && !dstInfo.IsDir() {
		return fmt.Errorf("dst is not a directory")
	}
	if err := os.Chmod(dst, srcInfo.Mode()); err != nil {
		return fmt.Errorf("chmod %s: %s", dst, err)
	}
	uid, gid := getFileOwners(srcInfo)
	if c.dstFileAndChildrenOwner != nil && c.dstFileAndChildrenOwner.overwrite {
		uid = c.dstFileAndChildrenOwner.uid
		gid = c.dstFileAndChildrenOwner.gid
	}
	if err := os.Chown(dst, uid, gid); err != nil {
		return fmt.Errorf("chown %s: %s", dst, err)
	}
	return nil
}