'use strict'; const fs = require('graceful-fs'); const path = require('path'); const mkdirsSync = require('../mkdirs').mkdirsSync; const utimesMillisSync = require('../util/utimes').utimesMillisSync; const stat = require('../util/stat'); function copySync(src, dest, opts) { if (typeof opts === 'function') { opts = { filter: opts }; } opts = opts || {}; opts.clobber = 'clobber' in opts ? !!opts.clobber : true; // default to true for now opts.overwrite = 'overwrite' in opts ? !!opts.overwrite : opts.clobber; // overwrite falls back to clobber // Warn about using preserveTimestamps on 32-bit node if (opts.preserveTimestamps && process.arch === 'ia32') { console.warn(`fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n see https://github.com/jprichardson/node-fs-extra/issues/269`); } const { srcStat, destStat } = stat.checkPathsSync(src, dest, 'copy'); stat.checkParentPathsSync(src, srcStat, dest, 'copy'); return handleFilterAndCopy(destStat, src, dest, opts); } function handleFilterAndCopy(destStat, src, dest, opts) { if (opts.filter && !opts.filter(src, dest)) return; const destParent = path.dirname(dest); if (!fs.existsSync(destParent)) mkdirsSync(destParent); return startCopy(destStat, src, dest, opts); } function startCopy(destStat, src, dest, opts) { if (opts.filter && !opts.filter(src, dest)) return; return getStats(destStat, src, dest, opts); } function getStats(destStat, src, dest, opts) { const statSync = opts.dereference ? fs.statSync : fs.lstatSync; const srcStat = statSync(src); if (srcStat.isDirectory()) return onDir(srcStat, destStat, src, dest, opts); else if ( srcStat.isFile() || srcStat.isCharacterDevice() || srcStat.isBlockDevice() ) return onFile(srcStat, destStat, src, dest, opts); else if (srcStat.isSymbolicLink()) return onLink(destStat, src, dest, opts); } function onFile(srcStat, destStat, src, dest, opts) { if (!destStat) return copyFile(srcStat, src, dest, opts); return mayCopyFile(srcStat, src, dest, opts); } function mayCopyFile(srcStat, src, dest, opts) { if (opts.overwrite) { fs.unlinkSync(dest); return copyFile(srcStat, src, dest, opts); } else if (opts.errorOnExist) { throw new Error(`'${dest}' already exists`); } } function copyFile(srcStat, src, dest, opts) { fs.copyFileSync(src, dest); if (opts.preserveTimestamps) handleTimestamps(srcStat.mode, src, dest); return setDestMode(dest, srcStat.mode); } function handleTimestamps(srcMode, src, dest) { // Make sure the file is writable before setting the timestamp // otherwise open fails with EPERM when invoked with 'r+' // (through utimes call) if (fileIsNotWritable(srcMode)) makeFileWritable(dest, srcMode); return setDestTimestamps(src, dest); } function fileIsNotWritable(srcMode) { return (srcMode & 0o200) === 0; } function makeFileWritable(dest, srcMode) { return setDestMode(dest, srcMode | 0o200); } function setDestMode(dest, srcMode) { return fs.chmodSync(dest, srcMode); } function setDestTimestamps(src, dest) { // The initial srcStat.atime cannot be trusted // because it is modified by the read(2) system call // (See https://nodejs.org/api/fs.html#fs_stat_time_values) const updatedSrcStat = fs.statSync(src); return utimesMillisSync(dest, updatedSrcStat.atime, updatedSrcStat.mtime); } function onDir(srcStat, destStat, src, dest, opts) { if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts); if (destStat && !destStat.isDirectory()) { throw new Error( `Cannot overwrite non-directory '${dest}' with directory '${src}'.` ); } return copyDir(src, dest, opts); } function mkDirAndCopy(srcMode, src, dest, opts) { fs.mkdirSync(dest); copyDir(src, dest, opts); return setDestMode(dest, srcMode); } function copyDir(src, dest, opts) { fs.readdirSync(src).forEach((item) => copyDirItem(item, src, dest, opts)); } function copyDirItem(item, src, dest, opts) { const srcItem = path.join(src, item); const destItem = path.join(dest, item); const { destStat } = stat.checkPathsSync(srcItem, destItem, 'copy'); return startCopy(destStat, srcItem, destItem, opts); } function onLink(destStat, src, dest, opts) { let resolvedSrc = fs.readlinkSync(src); if (opts.dereference) { resolvedSrc = path.resolve(process.cwd(), resolvedSrc); } if (!destStat) { return fs.symlinkSync(resolvedSrc, dest); } else { let resolvedDest; try { resolvedDest = fs.readlinkSync(dest); } catch (err) { // dest exists and is a regular file or directory, // Windows may throw UNKNOWN error. If dest already exists, // fs throws error anyway, so no need to guard against it here. if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return fs.symlinkSync(resolvedSrc, dest); throw err; } if (opts.dereference) { resolvedDest = path.resolve(process.cwd(), resolvedDest); } if (stat.isSrcSubdir(resolvedSrc, resolvedDest)) { throw new Error( `Cannot copy '${resolvedSrc}' to a subdirectory of itself, '${resolvedDest}'.` ); } // prevent copy if src is a subdir of dest since unlinking // dest in this case would result in removing src contents // and therefore a broken symlink would be created. if ( fs.statSync(dest).isDirectory() && stat.isSrcSubdir(resolvedDest, resolvedSrc) ) { throw new Error( `Cannot overwrite '${resolvedDest}' with '${resolvedSrc}'.` ); } return copyLink(resolvedSrc, dest); } } function copyLink(resolvedSrc, dest) { fs.unlinkSync(dest); return fs.symlinkSync(resolvedSrc, dest); } module.exports = copySync;