1'use strict'; 2 3// This file is a modified version of the fs-extra's copySync method. 4 5const { areIdentical, isSrcSubdir } = require('internal/fs/cp/cp'); 6const { codes } = require('internal/errors'); 7const { 8 os: { 9 errno: { 10 EEXIST, 11 EISDIR, 12 EINVAL, 13 ENOTDIR, 14 }, 15 }, 16} = internalBinding('constants'); 17const { 18 ERR_FS_CP_DIR_TO_NON_DIR, 19 ERR_FS_CP_EEXIST, 20 ERR_FS_CP_EINVAL, 21 ERR_FS_CP_FIFO_PIPE, 22 ERR_FS_CP_NON_DIR_TO_DIR, 23 ERR_FS_CP_SOCKET, 24 ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY, 25 ERR_FS_CP_UNKNOWN, 26 ERR_FS_EISDIR, 27 ERR_INVALID_RETURN_VALUE, 28} = codes; 29const { 30 chmodSync, 31 copyFileSync, 32 existsSync, 33 lstatSync, 34 mkdirSync, 35 opendirSync, 36 readlinkSync, 37 statSync, 38 symlinkSync, 39 unlinkSync, 40 utimesSync, 41} = require('fs'); 42const { 43 dirname, 44 isAbsolute, 45 join, 46 parse, 47 resolve, 48} = require('path'); 49const { isPromise } = require('util/types'); 50 51function cpSyncFn(src, dest, opts) { 52 // Warn about using preserveTimestamps on 32-bit node 53 if (opts.preserveTimestamps && process.arch === 'ia32') { 54 const warning = 'Using the preserveTimestamps option in 32-bit ' + 55 'node is not recommended'; 56 process.emitWarning(warning, 'TimestampPrecisionWarning'); 57 } 58 const { srcStat, destStat, skipped } = checkPathsSync(src, dest, opts); 59 if (skipped) return; 60 checkParentPathsSync(src, srcStat, dest); 61 return checkParentDir(destStat, src, dest, opts); 62} 63 64function checkPathsSync(src, dest, opts) { 65 if (opts.filter) { 66 const shouldCopy = opts.filter(src, dest); 67 if (isPromise(shouldCopy)) { 68 throw new ERR_INVALID_RETURN_VALUE('boolean', 'filter', shouldCopy); 69 } 70 if (!shouldCopy) return { __proto__: null, skipped: true }; 71 } 72 const { srcStat, destStat } = getStatsSync(src, dest, opts); 73 74 if (destStat) { 75 if (areIdentical(srcStat, destStat)) { 76 throw new ERR_FS_CP_EINVAL({ 77 message: 'src and dest cannot be the same', 78 path: dest, 79 syscall: 'cp', 80 errno: EINVAL, 81 code: 'EINVAL', 82 }); 83 } 84 if (srcStat.isDirectory() && !destStat.isDirectory()) { 85 throw new ERR_FS_CP_DIR_TO_NON_DIR({ 86 message: `cannot overwrite directory ${src} ` + 87 `with non-directory ${dest}`, 88 path: dest, 89 syscall: 'cp', 90 errno: EISDIR, 91 code: 'EISDIR', 92 }); 93 } 94 if (!srcStat.isDirectory() && destStat.isDirectory()) { 95 throw new ERR_FS_CP_NON_DIR_TO_DIR({ 96 message: `cannot overwrite non-directory ${src} ` + 97 `with directory ${dest}`, 98 path: dest, 99 syscall: 'cp', 100 errno: ENOTDIR, 101 code: 'ENOTDIR', 102 }); 103 } 104 } 105 106 if (srcStat.isDirectory() && isSrcSubdir(src, dest)) { 107 throw new ERR_FS_CP_EINVAL({ 108 message: `cannot copy ${src} to a subdirectory of self ${dest}`, 109 path: dest, 110 syscall: 'cp', 111 errno: EINVAL, 112 code: 'EINVAL', 113 }); 114 } 115 return { __proto__: null, srcStat, destStat, skipped: false }; 116} 117 118function getStatsSync(src, dest, opts) { 119 let destStat; 120 const statFunc = opts.dereference ? 121 (file) => statSync(file, { bigint: true }) : 122 (file) => lstatSync(file, { bigint: true }); 123 const srcStat = statFunc(src); 124 try { 125 destStat = statFunc(dest); 126 } catch (err) { 127 if (err.code === 'ENOENT') return { srcStat, destStat: null }; 128 throw err; 129 } 130 return { srcStat, destStat }; 131} 132 133function checkParentPathsSync(src, srcStat, dest) { 134 const srcParent = resolve(dirname(src)); 135 const destParent = resolve(dirname(dest)); 136 if (destParent === srcParent || destParent === parse(destParent).root) return; 137 let destStat; 138 try { 139 destStat = statSync(destParent, { bigint: true }); 140 } catch (err) { 141 if (err.code === 'ENOENT') return; 142 throw err; 143 } 144 if (areIdentical(srcStat, destStat)) { 145 throw new ERR_FS_CP_EINVAL({ 146 message: `cannot copy ${src} to a subdirectory of self ${dest}`, 147 path: dest, 148 syscall: 'cp', 149 errno: EINVAL, 150 code: 'EINVAL', 151 }); 152 } 153 return checkParentPathsSync(src, srcStat, destParent); 154} 155 156function checkParentDir(destStat, src, dest, opts) { 157 const destParent = dirname(dest); 158 if (!existsSync(destParent)) mkdirSync(destParent, { recursive: true }); 159 return getStats(destStat, src, dest, opts); 160} 161 162function getStats(destStat, src, dest, opts) { 163 const statSyncFn = opts.dereference ? statSync : lstatSync; 164 const srcStat = statSyncFn(src); 165 166 if (srcStat.isDirectory() && opts.recursive) { 167 return onDir(srcStat, destStat, src, dest, opts); 168 } else if (srcStat.isDirectory()) { 169 throw new ERR_FS_EISDIR({ 170 message: `${src} is a directory (not copied)`, 171 path: src, 172 syscall: 'cp', 173 errno: EINVAL, 174 code: 'EISDIR', 175 }); 176 } else if (srcStat.isFile() || 177 srcStat.isCharacterDevice() || 178 srcStat.isBlockDevice()) { 179 return onFile(srcStat, destStat, src, dest, opts); 180 } else if (srcStat.isSymbolicLink()) { 181 return onLink(destStat, src, dest, opts); 182 } else if (srcStat.isSocket()) { 183 throw new ERR_FS_CP_SOCKET({ 184 message: `cannot copy a socket file: ${dest}`, 185 path: dest, 186 syscall: 'cp', 187 errno: EINVAL, 188 code: 'EINVAL', 189 }); 190 } else if (srcStat.isFIFO()) { 191 throw new ERR_FS_CP_FIFO_PIPE({ 192 message: `cannot copy a FIFO pipe: ${dest}`, 193 path: dest, 194 syscall: 'cp', 195 errno: EINVAL, 196 code: 'EINVAL', 197 }); 198 } 199 throw new ERR_FS_CP_UNKNOWN({ 200 message: `cannot copy an unknown file type: ${dest}`, 201 path: dest, 202 syscall: 'cp', 203 errno: EINVAL, 204 code: 'EINVAL', 205 }); 206} 207 208function onFile(srcStat, destStat, src, dest, opts) { 209 if (!destStat) return copyFile(srcStat, src, dest, opts); 210 return mayCopyFile(srcStat, src, dest, opts); 211} 212 213function mayCopyFile(srcStat, src, dest, opts) { 214 if (opts.force) { 215 unlinkSync(dest); 216 return copyFile(srcStat, src, dest, opts); 217 } else if (opts.errorOnExist) { 218 throw new ERR_FS_CP_EEXIST({ 219 message: `${dest} already exists`, 220 path: dest, 221 syscall: 'cp', 222 errno: EEXIST, 223 code: 'EEXIST', 224 }); 225 } 226} 227 228function copyFile(srcStat, src, dest, opts) { 229 copyFileSync(src, dest, opts.mode); 230 if (opts.preserveTimestamps) handleTimestamps(srcStat.mode, src, dest); 231 return setDestMode(dest, srcStat.mode); 232} 233 234function handleTimestamps(srcMode, src, dest) { 235 // Make sure the file is writable before setting the timestamp 236 // otherwise open fails with EPERM when invoked with 'r+' 237 // (through utimes call) 238 if (fileIsNotWritable(srcMode)) makeFileWritable(dest, srcMode); 239 return setDestTimestamps(src, dest); 240} 241 242function fileIsNotWritable(srcMode) { 243 return (srcMode & 0o200) === 0; 244} 245 246function makeFileWritable(dest, srcMode) { 247 return setDestMode(dest, srcMode | 0o200); 248} 249 250function setDestMode(dest, srcMode) { 251 return chmodSync(dest, srcMode); 252} 253 254function setDestTimestamps(src, dest) { 255 // The initial srcStat.atime cannot be trusted 256 // because it is modified by the read(2) system call 257 // (See https://nodejs.org/api/fs.html#fs_stat_time_values) 258 const updatedSrcStat = statSync(src); 259 return utimesSync(dest, updatedSrcStat.atime, updatedSrcStat.mtime); 260} 261 262function onDir(srcStat, destStat, src, dest, opts) { 263 if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts); 264 return copyDir(src, dest, opts); 265} 266 267function mkDirAndCopy(srcMode, src, dest, opts) { 268 mkdirSync(dest); 269 copyDir(src, dest, opts); 270 return setDestMode(dest, srcMode); 271} 272 273function copyDir(src, dest, opts) { 274 const dir = opendirSync(src); 275 276 try { 277 let dirent; 278 279 while ((dirent = dir.readSync()) !== null) { 280 const { name } = dirent; 281 const srcItem = join(src, name); 282 const destItem = join(dest, name); 283 const { destStat, skipped } = checkPathsSync(srcItem, destItem, opts); 284 if (!skipped) getStats(destStat, srcItem, destItem, opts); 285 } 286 } finally { 287 dir.closeSync(); 288 } 289} 290 291function onLink(destStat, src, dest, opts) { 292 let resolvedSrc = readlinkSync(src); 293 if (!opts.verbatimSymlinks && !isAbsolute(resolvedSrc)) { 294 resolvedSrc = resolve(dirname(src), resolvedSrc); 295 } 296 if (!destStat) { 297 return symlinkSync(resolvedSrc, dest); 298 } 299 let resolvedDest; 300 try { 301 resolvedDest = readlinkSync(dest); 302 } catch (err) { 303 // Dest exists and is a regular file or directory, 304 // Windows may throw UNKNOWN error. If dest already exists, 305 // fs throws error anyway, so no need to guard against it here. 306 if (err.code === 'EINVAL' || err.code === 'UNKNOWN') { 307 return symlinkSync(resolvedSrc, dest); 308 } 309 throw err; 310 } 311 if (!isAbsolute(resolvedDest)) { 312 resolvedDest = resolve(dirname(dest), resolvedDest); 313 } 314 if (isSrcSubdir(resolvedSrc, resolvedDest)) { 315 throw new ERR_FS_CP_EINVAL({ 316 message: `cannot copy ${resolvedSrc} to a subdirectory of self ` + 317 `${resolvedDest}`, 318 path: dest, 319 syscall: 'cp', 320 errno: EINVAL, 321 code: 'EINVAL', 322 }); 323 } 324 // Prevent copy if src is a subdir of dest since unlinking 325 // dest in this case would result in removing src contents 326 // and therefore a broken symlink would be created. 327 if (statSync(dest).isDirectory() && isSrcSubdir(resolvedDest, resolvedSrc)) { 328 throw new ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY({ 329 message: `cannot overwrite ${resolvedDest} with ${resolvedSrc}`, 330 path: dest, 331 syscall: 'cp', 332 errno: EINVAL, 333 code: 'EINVAL', 334 }); 335 } 336 return copyLink(resolvedSrc, dest); 337} 338 339function copyLink(resolvedSrc, dest) { 340 unlinkSync(dest); 341 return symlinkSync(resolvedSrc, dest); 342} 343 344module.exports = { cpSyncFn }; 345