1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package cgotest
6
7import (
8	"io"
9	"os"
10	"path/filepath"
11	"strings"
12)
13
14// OverlayDir makes a minimal-overhead copy of srcRoot in which new files may be added.
15func OverlayDir(dstRoot, srcRoot string) error {
16	dstRoot = filepath.Clean(dstRoot)
17	if err := os.MkdirAll(dstRoot, 0777); err != nil {
18		return err
19	}
20
21	srcRoot, err := filepath.Abs(srcRoot)
22	if err != nil {
23		return err
24	}
25
26	return filepath.Walk(srcRoot, func(srcPath string, info os.FileInfo, err error) error {
27		if err != nil || srcPath == srcRoot {
28			return err
29		}
30
31		suffix := strings.TrimPrefix(srcPath, srcRoot)
32		for len(suffix) > 0 && suffix[0] == filepath.Separator {
33			suffix = suffix[1:]
34		}
35		dstPath := filepath.Join(dstRoot, suffix)
36
37		perm := info.Mode() & os.ModePerm
38		if info.Mode()&os.ModeSymlink != 0 {
39			info, err = os.Stat(srcPath)
40			if err != nil {
41				return err
42			}
43			perm = info.Mode() & os.ModePerm
44		}
45
46		// Always copy directories (don't symlink them).
47		// If we add a file in the overlay, we don't want to add it in the original.
48		if info.IsDir() {
49			return os.MkdirAll(dstPath, perm|0200)
50		}
51
52		// If the OS supports symlinks, use them instead of copying bytes.
53		if err := os.Symlink(srcPath, dstPath); err == nil {
54			return nil
55		}
56
57		// Otherwise, copy the bytes.
58		src, err := os.Open(srcPath)
59		if err != nil {
60			return err
61		}
62		defer src.Close()
63
64		dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
65		if err != nil {
66			return err
67		}
68
69		_, err = io.Copy(dst, src)
70		if closeErr := dst.Close(); err == nil {
71			err = closeErr
72		}
73		return err
74	})
75}
76