/ cloudformation-templates / node_modules / aws-cdk / node_modules / fs-extra / lib / util / stat.js
stat.js
1 'use strict' 2 3 const fs = require('../fs') 4 const path = require('path') 5 const util = require('util') 6 const atLeastNode = require('at-least-node') 7 8 const nodeSupportsBigInt = atLeastNode('10.5.0') 9 const stat = (file) => nodeSupportsBigInt ? fs.stat(file, { bigint: true }) : fs.stat(file) 10 const statSync = (file) => nodeSupportsBigInt ? fs.statSync(file, { bigint: true }) : fs.statSync(file) 11 12 function getStats (src, dest) { 13 return Promise.all([ 14 stat(src), 15 stat(dest).catch(err => { 16 if (err.code === 'ENOENT') return null 17 throw err 18 }) 19 ]).then(([srcStat, destStat]) => ({ srcStat, destStat })) 20 } 21 22 function getStatsSync (src, dest) { 23 let destStat 24 const srcStat = statSync(src) 25 try { 26 destStat = statSync(dest) 27 } catch (err) { 28 if (err.code === 'ENOENT') return { srcStat, destStat: null } 29 throw err 30 } 31 return { srcStat, destStat } 32 } 33 34 function checkPaths (src, dest, funcName, cb) { 35 util.callbackify(getStats)(src, dest, (err, stats) => { 36 if (err) return cb(err) 37 const { srcStat, destStat } = stats 38 if (destStat && areIdentical(srcStat, destStat)) { 39 return cb(new Error('Source and destination must not be the same.')) 40 } 41 if (srcStat.isDirectory() && isSrcSubdir(src, dest)) { 42 return cb(new Error(errMsg(src, dest, funcName))) 43 } 44 return cb(null, { srcStat, destStat }) 45 }) 46 } 47 48 function checkPathsSync (src, dest, funcName) { 49 const { srcStat, destStat } = getStatsSync(src, dest) 50 if (destStat && areIdentical(srcStat, destStat)) { 51 throw new Error('Source and destination must not be the same.') 52 } 53 if (srcStat.isDirectory() && isSrcSubdir(src, dest)) { 54 throw new Error(errMsg(src, dest, funcName)) 55 } 56 return { srcStat, destStat } 57 } 58 59 // recursively check if dest parent is a subdirectory of src. 60 // It works for all file types including symlinks since it 61 // checks the src and dest inodes. It starts from the deepest 62 // parent and stops once it reaches the src parent or the root path. 63 function checkParentPaths (src, srcStat, dest, funcName, cb) { 64 const srcParent = path.resolve(path.dirname(src)) 65 const destParent = path.resolve(path.dirname(dest)) 66 if (destParent === srcParent || destParent === path.parse(destParent).root) return cb() 67 const callback = (err, destStat) => { 68 if (err) { 69 if (err.code === 'ENOENT') return cb() 70 return cb(err) 71 } 72 if (areIdentical(srcStat, destStat)) { 73 return cb(new Error(errMsg(src, dest, funcName))) 74 } 75 return checkParentPaths(src, srcStat, destParent, funcName, cb) 76 } 77 if (nodeSupportsBigInt) fs.stat(destParent, { bigint: true }, callback) 78 else fs.stat(destParent, callback) 79 } 80 81 function checkParentPathsSync (src, srcStat, dest, funcName) { 82 const srcParent = path.resolve(path.dirname(src)) 83 const destParent = path.resolve(path.dirname(dest)) 84 if (destParent === srcParent || destParent === path.parse(destParent).root) return 85 let destStat 86 try { 87 destStat = statSync(destParent) 88 } catch (err) { 89 if (err.code === 'ENOENT') return 90 throw err 91 } 92 if (areIdentical(srcStat, destStat)) { 93 throw new Error(errMsg(src, dest, funcName)) 94 } 95 return checkParentPathsSync(src, srcStat, destParent, funcName) 96 } 97 98 function areIdentical (srcStat, destStat) { 99 if (destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) { 100 if (nodeSupportsBigInt || destStat.ino < Number.MAX_SAFE_INTEGER) { 101 // definitive answer 102 return true 103 } 104 // Use additional heuristics if we can't use 'bigint'. 105 // Different 'ino' could be represented the same if they are >= Number.MAX_SAFE_INTEGER 106 // See issue 657 107 if (destStat.size === srcStat.size && 108 destStat.mode === srcStat.mode && 109 destStat.nlink === srcStat.nlink && 110 destStat.atimeMs === srcStat.atimeMs && 111 destStat.mtimeMs === srcStat.mtimeMs && 112 destStat.ctimeMs === srcStat.ctimeMs && 113 destStat.birthtimeMs === srcStat.birthtimeMs) { 114 // heuristic answer 115 return true 116 } 117 } 118 return false 119 } 120 121 // return true if dest is a subdir of src, otherwise false. 122 // It only checks the path strings. 123 function isSrcSubdir (src, dest) { 124 const srcArr = path.resolve(src).split(path.sep).filter(i => i) 125 const destArr = path.resolve(dest).split(path.sep).filter(i => i) 126 return srcArr.reduce((acc, cur, i) => acc && destArr[i] === cur, true) 127 } 128 129 function errMsg (src, dest, funcName) { 130 return `Cannot ${funcName} '${src}' to a subdirectory of itself, '${dest}'.` 131 } 132 133 module.exports = { 134 checkPaths, 135 checkPathsSync, 136 checkParentPaths, 137 checkParentPathsSync, 138 isSrcSubdir 139 }