1package copy
  2
  3import (
  4	"io"
  5	"io/fs"
  6	"io/ioutil"
  7	"os"
  8	"path/filepath"
  9	"time"
 10)
 11
 12type timespec struct {
 13	Mtime time.Time
 14	Atime time.Time
 15	Ctime time.Time
 16}
 17
 18// Copy copies src to dest, doesn't matter if src is a directory or a file.
 19func Copy(src, dest string, opts ...Options) error {
 20	opt := assureOptions(src, dest, opts...)
 21	if opt.FS != nil {
 22		info, err := fs.Stat(opt.FS, src)
 23		if err != nil {
 24			return onError(src, dest, err, opt)
 25		}
 26		return switchboard(src, dest, info, opt)
 27	}
 28	info, err := os.Lstat(src)
 29	if err != nil {
 30		return onError(src, dest, err, opt)
 31	}
 32	return switchboard(src, dest, info, opt)
 33}
 34
 35// switchboard switches proper copy functions regarding file type, etc...
 36// If there would be anything else here, add a case to this switchboard.
 37func switchboard(src, dest string, info os.FileInfo, opt Options) (err error) {
 38	if info.Mode()&os.ModeDevice != 0 && !opt.Specials {
 39		return onError(src, dest, err, opt)
 40	}
 41
 42	switch {
 43	case info.Mode()&os.ModeSymlink != 0:
 44		err = onsymlink(src, dest, opt)
 45	case info.IsDir():
 46		err = dcopy(src, dest, info, opt)
 47	case info.Mode()&os.ModeNamedPipe != 0:
 48		err = pcopy(dest, info)
 49	default:
 50		err = fcopy(src, dest, info, opt)
 51	}
 52
 53	return onError(src, dest, err, opt)
 54}
 55
 56// copyNextOrSkip decide if this src should be copied or not.
 57// Because this "copy" could be called recursively,
 58// "info" MUST be given here, NOT nil.
 59func copyNextOrSkip(src, dest string, info os.FileInfo, opt Options) error {
 60	if opt.Skip != nil {
 61		skip, err := opt.Skip(info, src, dest)
 62		if err != nil {
 63			return err
 64		}
 65		if skip {
 66			return nil
 67		}
 68	}
 69	return switchboard(src, dest, info, opt)
 70}
 71
 72// fcopy is for just a file,
 73// with considering existence of parent directory
 74// and file permission.
 75func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) {
 76
 77	var readcloser io.ReadCloser
 78	if opt.FS != nil {
 79		readcloser, err = opt.FS.Open(src)
 80	} else {
 81		readcloser, err = os.Open(src)
 82	}
 83	if err != nil {
 84		if os.IsNotExist(err) {
 85			return nil
 86		}
 87		return
 88	}
 89	defer fclose(readcloser, &err)
 90
 91	if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
 92		return
 93	}
 94
 95	f, err := os.Create(dest)
 96	if err != nil {
 97		return
 98	}
 99	defer fclose(f, &err)
100
101	chmodfunc, err := opt.PermissionControl(info, dest)
102	if err != nil {
103		return err
104	}
105	chmodfunc(&err)
106
107	var buf []byte = nil
108	var w io.Writer = f
109	var r io.Reader = readcloser
110
111	if opt.WrapReader != nil {
112		r = opt.WrapReader(r)
113	}
114
115	if opt.CopyBufferSize != 0 {
116		buf = make([]byte, opt.CopyBufferSize)
117		// Disable using `ReadFrom` by io.CopyBuffer.
118		// See https://github.com/otiai10/copy/pull/60#discussion_r627320811 for more details.
119		w = struct{ io.Writer }{f}
120		// r = struct{ io.Reader }{s}
121	}
122
123	if _, err = io.CopyBuffer(w, r, buf); err != nil {
124		return err
125	}
126
127	if opt.Sync {
128		err = f.Sync()
129	}
130
131	if opt.PreserveOwner {
132		if err := preserveOwner(src, dest, info); err != nil {
133			return err
134		}
135	}
136	if opt.PreserveTimes {
137		if err := preserveTimes(info, dest); err != nil {
138			return err
139		}
140	}
141
142	return
143}
144
145// dcopy is for a directory,
146// with scanning contents inside the directory
147// and pass everything to "copy" recursively.
148func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
149	if skip, err := onDirExists(opt, srcdir, destdir); err != nil {
150		return err
151	} else if skip {
152		return nil
153	}
154
155	// Make dest dir with 0755 so that everything writable.
156	chmodfunc, err := opt.PermissionControl(info, destdir)
157	if err != nil {
158		return err
159	}
160	defer chmodfunc(&err)
161
162	var contents []os.FileInfo
163	if opt.FS != nil {
164		entries, err := fs.ReadDir(opt.FS, srcdir)
165		if err != nil {
166			return err
167		}
168		for _, e := range entries {
169			info, err := e.Info()
170			if err != nil {
171				return err
172			}
173			contents = append(contents, info)
174		}
175	} else {
176		contents, err = ioutil.ReadDir(srcdir)
177	}
178
179	if err != nil {
180		if os.IsNotExist(err) {
181			return nil
182		}
183		return
184	}
185
186	for _, content := range contents {
187		cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name())
188
189		if err = copyNextOrSkip(cs, cd, content, opt); err != nil {
190			// If any error, exit immediately
191			return
192		}
193	}
194
195	if opt.PreserveTimes {
196		if err := preserveTimes(info, destdir); err != nil {
197			return err
198		}
199	}
200
201	if opt.PreserveOwner {
202		if err := preserveOwner(srcdir, destdir, info); err != nil {
203			return err
204		}
205	}
206
207	return
208}
209
210func onDirExists(opt Options, srcdir, destdir string) (bool, error) {
211	_, err := os.Stat(destdir)
212	if err == nil && opt.OnDirExists != nil && destdir != opt.intent.dest {
213		switch opt.OnDirExists(srcdir, destdir) {
214		case Replace:
215			if err := os.RemoveAll(destdir); err != nil {
216				return false, err
217			}
218		case Untouchable:
219			return true, nil
220		} // case "Merge" is default behaviour. Go through.
221	} else if err != nil && !os.IsNotExist(err) {
222		return true, err // Unwelcome error type...!
223	}
224	return false, nil
225}
226
227func onsymlink(src, dest string, opt Options) error {
228	switch opt.OnSymlink(src) {
229	case Shallow:
230		if err := lcopy(src, dest); err != nil {
231			return err
232		}
233		if opt.PreserveTimes {
234			return preserveLtimes(src, dest)
235		}
236		return nil
237	case Deep:
238		orig, err := os.Readlink(src)
239		if err != nil {
240			return err
241		}
242		info, err := os.Lstat(orig)
243		if err != nil {
244			return err
245		}
246		return copyNextOrSkip(orig, dest, info, opt)
247	case Skip:
248		fallthrough
249	default:
250		return nil // do nothing
251	}
252}
253
254// lcopy is for a symlink,
255// with just creating a new symlink by replicating src symlink.
256func lcopy(src, dest string) error {
257	src, err := os.Readlink(src)
258	if err != nil {
259		if os.IsNotExist(err) {
260			return nil
261		}
262		return err
263	}
264	return os.Symlink(src, dest)
265}
266
267// fclose ANYHOW closes file,
268// with asiging error raised during Close,
269// BUT respecting the error already reported.
270func fclose(f io.Closer, reported *error) {
271	if err := f.Close(); *reported == nil {
272		*reported = err
273	}
274}
275
276// onError lets caller to handle errors
277// occured when copying a file.
278func onError(src, dest string, err error, opt Options) error {
279	if opt.OnError == nil {
280		return err
281	}
282
283	return opt.OnError(src, dest, err)
284}