1package fileutils
2
3import (
4	"fmt"
5	"io"
6	"os"
7	"path/filepath"
8	"syscall"
9)
10
11// CopyFile copies the file at source to dest
12func CopyFile(source string, dest string) error {
13	si, err := os.Lstat(source)
14	if err != nil {
15		return err
16	}
17
18	st, ok := si.Sys().(*syscall.Stat_t)
19	if !ok {
20		return fmt.Errorf("could not convert to syscall.Stat_t")
21	}
22
23	uid := int(st.Uid)
24	gid := int(st.Gid)
25
26	// Handle symlinks
27	if si.Mode()&os.ModeSymlink != 0 {
28		target, err := os.Readlink(source)
29		if err != nil {
30			return err
31		}
32		if err := os.Symlink(target, dest); err != nil {
33			return err
34		}
35	}
36
37	// Handle device files
38	if st.Mode&syscall.S_IFMT == syscall.S_IFBLK || st.Mode&syscall.S_IFMT == syscall.S_IFCHR {
39		devMajor := int64(major(uint64(st.Rdev)))
40		devMinor := int64(minor(uint64(st.Rdev)))
41		mode := uint32(si.Mode() & 07777)
42		if st.Mode&syscall.S_IFMT == syscall.S_IFBLK {
43			mode |= syscall.S_IFBLK
44		}
45		if st.Mode&syscall.S_IFMT == syscall.S_IFCHR {
46			mode |= syscall.S_IFCHR
47		}
48		if err := syscall.Mknod(dest, mode, int(mkdev(devMajor, devMinor))); err != nil {
49			return err
50		}
51	}
52
53	// Handle regular files
54	if si.Mode().IsRegular() {
55		sf, err := os.Open(source)
56		if err != nil {
57			return err
58		}
59		defer sf.Close()
60
61		df, err := os.Create(dest)
62		if err != nil {
63			return err
64		}
65		defer df.Close()
66
67		_, err = io.Copy(df, sf)
68		if err != nil {
69			return err
70		}
71	}
72
73	// Chown the file
74	if err := os.Lchown(dest, uid, gid); err != nil {
75		return err
76	}
77
78	// Chmod the file
79	if !(si.Mode()&os.ModeSymlink == os.ModeSymlink) {
80		if err := os.Chmod(dest, si.Mode()); err != nil {
81			return err
82		}
83	}
84
85	return nil
86}
87
88// CopyDirectory copies the files under the source directory
89// to dest directory. The dest directory is created if it
90// does not exist.
91func CopyDirectory(source string, dest string) error {
92	fi, err := os.Stat(source)
93	if err != nil {
94		return err
95	}
96
97	// Get owner.
98	st, ok := fi.Sys().(*syscall.Stat_t)
99	if !ok {
100		return fmt.Errorf("could not convert to syscall.Stat_t")
101	}
102
103	// We have to pick an owner here anyway.
104	if err := MkdirAllNewAs(dest, fi.Mode(), int(st.Uid), int(st.Gid)); err != nil {
105		return err
106	}
107
108	return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
109		if err != nil {
110			return err
111		}
112
113		// Get the relative path
114		relPath, err := filepath.Rel(source, path)
115		if err != nil {
116			return nil
117		}
118
119		if info.IsDir() {
120			// Skip the source directory.
121			if path != source {
122				// Get the owner.
123				st, ok := info.Sys().(*syscall.Stat_t)
124				if !ok {
125					return fmt.Errorf("could not convert to syscall.Stat_t")
126				}
127
128				uid := int(st.Uid)
129				gid := int(st.Gid)
130
131				if err := os.Mkdir(filepath.Join(dest, relPath), info.Mode()); err != nil {
132					return err
133				}
134
135				if err := os.Lchown(filepath.Join(dest, relPath), uid, gid); err != nil {
136					return err
137				}
138			}
139			return nil
140		}
141
142		return CopyFile(path, filepath.Join(dest, relPath))
143	})
144}
145
146// Gives a number indicating the device driver to be used to access the passed device
147func major(device uint64) uint64 {
148	return (device >> 8) & 0xfff
149}
150
151// Gives a number that serves as a flag to the device driver for the passed device
152func minor(device uint64) uint64 {
153	return (device & 0xff) | ((device >> 12) & 0xfff00)
154}
155
156func mkdev(major int64, minor int64) uint32 {
157	return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
158}
159