source: node_modules/fs-extra/lib/util/stat.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: 4.5 KB
Line 
1'use strict'
2
3const fs = require('../fs')
4const path = require('path')
5const util = require('util')
6const atLeastNode = require('at-least-node')
7
8const nodeSupportsBigInt = atLeastNode('10.5.0')
9const stat = (file) => nodeSupportsBigInt ? fs.stat(file, { bigint: true }) : fs.stat(file)
10const statSync = (file) => nodeSupportsBigInt ? fs.statSync(file, { bigint: true }) : fs.statSync(file)
11
12function 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
22function 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
34function 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
48function 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.
63function 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
81function 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
98function 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.
123function 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
129function errMsg (src, dest, funcName) {
130 return `Cannot ${funcName} '${src}' to a subdirectory of itself, '${dest}'.`
131}
132
133module.exports = {
134 checkPaths,
135 checkPathsSync,
136 checkParentPaths,
137 checkParentPathsSync,
138 isSrcSubdir
139}
Note: See TracBrowser for help on using the repository browser.