source: node_modules/fs-extra/lib/copy/copy.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 7.4 KB
Line 
1'use strict'
2
3const fs = require('graceful-fs')
4const path = require('path')
5const mkdirs = require('../mkdirs').mkdirs
6const pathExists = require('../path-exists').pathExists
7const utimesMillis = require('../util/utimes').utimesMillis
8const stat = require('../util/stat')
9
10function copy (src, dest, opts, cb) {
11 if (typeof opts === 'function' && !cb) {
12 cb = opts
13 opts = {}
14 } else if (typeof opts === 'function') {
15 opts = { filter: opts }
16 }
17
18 cb = cb || function () {}
19 opts = opts || {}
20
21 opts.clobber = 'clobber' in opts ? !!opts.clobber : true // default to true for now
22 opts.overwrite = 'overwrite' in opts ? !!opts.overwrite : opts.clobber // overwrite falls back to clobber
23
24 // Warn about using preserveTimestamps on 32-bit node
25 if (opts.preserveTimestamps && process.arch === 'ia32') {
26 console.warn(`fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n
27 see https://github.com/jprichardson/node-fs-extra/issues/269`)
28 }
29
30 stat.checkPaths(src, dest, 'copy', (err, stats) => {
31 if (err) return cb(err)
32 const { srcStat, destStat } = stats
33 stat.checkParentPaths(src, srcStat, dest, 'copy', err => {
34 if (err) return cb(err)
35 if (opts.filter) return handleFilter(checkParentDir, destStat, src, dest, opts, cb)
36 return checkParentDir(destStat, src, dest, opts, cb)
37 })
38 })
39}
40
41function checkParentDir (destStat, src, dest, opts, cb) {
42 const destParent = path.dirname(dest)
43 pathExists(destParent, (err, dirExists) => {
44 if (err) return cb(err)
45 if (dirExists) return startCopy(destStat, src, dest, opts, cb)
46 mkdirs(destParent, err => {
47 if (err) return cb(err)
48 return startCopy(destStat, src, dest, opts, cb)
49 })
50 })
51}
52
53function handleFilter (onInclude, destStat, src, dest, opts, cb) {
54 Promise.resolve(opts.filter(src, dest)).then(include => {
55 if (include) return onInclude(destStat, src, dest, opts, cb)
56 return cb()
57 }, error => cb(error))
58}
59
60function startCopy (destStat, src, dest, opts, cb) {
61 if (opts.filter) return handleFilter(getStats, destStat, src, dest, opts, cb)
62 return getStats(destStat, src, dest, opts, cb)
63}
64
65function getStats (destStat, src, dest, opts, cb) {
66 const stat = opts.dereference ? fs.stat : fs.lstat
67 stat(src, (err, srcStat) => {
68 if (err) return cb(err)
69
70 if (srcStat.isDirectory()) return onDir(srcStat, destStat, src, dest, opts, cb)
71 else if (srcStat.isFile() ||
72 srcStat.isCharacterDevice() ||
73 srcStat.isBlockDevice()) return onFile(srcStat, destStat, src, dest, opts, cb)
74 else if (srcStat.isSymbolicLink()) return onLink(destStat, src, dest, opts, cb)
75 })
76}
77
78function onFile (srcStat, destStat, src, dest, opts, cb) {
79 if (!destStat) return copyFile(srcStat, src, dest, opts, cb)
80 return mayCopyFile(srcStat, src, dest, opts, cb)
81}
82
83function mayCopyFile (srcStat, src, dest, opts, cb) {
84 if (opts.overwrite) {
85 fs.unlink(dest, err => {
86 if (err) return cb(err)
87 return copyFile(srcStat, src, dest, opts, cb)
88 })
89 } else if (opts.errorOnExist) {
90 return cb(new Error(`'${dest}' already exists`))
91 } else return cb()
92}
93
94function copyFile (srcStat, src, dest, opts, cb) {
95 fs.copyFile(src, dest, err => {
96 if (err) return cb(err)
97 if (opts.preserveTimestamps) return handleTimestampsAndMode(srcStat.mode, src, dest, cb)
98 return setDestMode(dest, srcStat.mode, cb)
99 })
100}
101
102function handleTimestampsAndMode (srcMode, src, dest, cb) {
103 // Make sure the file is writable before setting the timestamp
104 // otherwise open fails with EPERM when invoked with 'r+'
105 // (through utimes call)
106 if (fileIsNotWritable(srcMode)) {
107 return makeFileWritable(dest, srcMode, err => {
108 if (err) return cb(err)
109 return setDestTimestampsAndMode(srcMode, src, dest, cb)
110 })
111 }
112 return setDestTimestampsAndMode(srcMode, src, dest, cb)
113}
114
115function fileIsNotWritable (srcMode) {
116 return (srcMode & 0o200) === 0
117}
118
119function makeFileWritable (dest, srcMode, cb) {
120 return setDestMode(dest, srcMode | 0o200, cb)
121}
122
123function setDestTimestampsAndMode (srcMode, src, dest, cb) {
124 setDestTimestamps(src, dest, err => {
125 if (err) return cb(err)
126 return setDestMode(dest, srcMode, cb)
127 })
128}
129
130function setDestMode (dest, srcMode, cb) {
131 return fs.chmod(dest, srcMode, cb)
132}
133
134function setDestTimestamps (src, dest, cb) {
135 // The initial srcStat.atime cannot be trusted
136 // because it is modified by the read(2) system call
137 // (See https://nodejs.org/api/fs.html#fs_stat_time_values)
138 fs.stat(src, (err, updatedSrcStat) => {
139 if (err) return cb(err)
140 return utimesMillis(dest, updatedSrcStat.atime, updatedSrcStat.mtime, cb)
141 })
142}
143
144function onDir (srcStat, destStat, src, dest, opts, cb) {
145 if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts, cb)
146 if (destStat && !destStat.isDirectory()) {
147 return cb(new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`))
148 }
149 return copyDir(src, dest, opts, cb)
150}
151
152function mkDirAndCopy (srcMode, src, dest, opts, cb) {
153 fs.mkdir(dest, err => {
154 if (err) return cb(err)
155 copyDir(src, dest, opts, err => {
156 if (err) return cb(err)
157 return setDestMode(dest, srcMode, cb)
158 })
159 })
160}
161
162function copyDir (src, dest, opts, cb) {
163 fs.readdir(src, (err, items) => {
164 if (err) return cb(err)
165 return copyDirItems(items, src, dest, opts, cb)
166 })
167}
168
169function copyDirItems (items, src, dest, opts, cb) {
170 const item = items.pop()
171 if (!item) return cb()
172 return copyDirItem(items, item, src, dest, opts, cb)
173}
174
175function copyDirItem (items, item, src, dest, opts, cb) {
176 const srcItem = path.join(src, item)
177 const destItem = path.join(dest, item)
178 stat.checkPaths(srcItem, destItem, 'copy', (err, stats) => {
179 if (err) return cb(err)
180 const { destStat } = stats
181 startCopy(destStat, srcItem, destItem, opts, err => {
182 if (err) return cb(err)
183 return copyDirItems(items, src, dest, opts, cb)
184 })
185 })
186}
187
188function onLink (destStat, src, dest, opts, cb) {
189 fs.readlink(src, (err, resolvedSrc) => {
190 if (err) return cb(err)
191 if (opts.dereference) {
192 resolvedSrc = path.resolve(process.cwd(), resolvedSrc)
193 }
194
195 if (!destStat) {
196 return fs.symlink(resolvedSrc, dest, cb)
197 } else {
198 fs.readlink(dest, (err, resolvedDest) => {
199 if (err) {
200 // dest exists and is a regular file or directory,
201 // Windows may throw UNKNOWN error. If dest already exists,
202 // fs throws error anyway, so no need to guard against it here.
203 if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return fs.symlink(resolvedSrc, dest, cb)
204 return cb(err)
205 }
206 if (opts.dereference) {
207 resolvedDest = path.resolve(process.cwd(), resolvedDest)
208 }
209 if (stat.isSrcSubdir(resolvedSrc, resolvedDest)) {
210 return cb(new Error(`Cannot copy '${resolvedSrc}' to a subdirectory of itself, '${resolvedDest}'.`))
211 }
212
213 // do not copy if src is a subdir of dest since unlinking
214 // dest in this case would result in removing src contents
215 // and therefore a broken symlink would be created.
216 if (destStat.isDirectory() && stat.isSrcSubdir(resolvedDest, resolvedSrc)) {
217 return cb(new Error(`Cannot overwrite '${resolvedDest}' with '${resolvedSrc}'.`))
218 }
219 return copyLink(resolvedSrc, dest, cb)
220 })
221 }
222 })
223}
224
225function copyLink (resolvedSrc, dest, cb) {
226 fs.unlink(dest, err => {
227 if (err) return cb(err)
228 return fs.symlink(resolvedSrc, dest, cb)
229 })
230}
231
232module.exports = copy
Note: See TracBrowser for help on using the repository browser.