Skip to content

Commit d466ab6

Browse files
committed
fix: fix bug on utimes, futimes, add support of lutimes
lutimes is available on Nodejs v14.5.0+ and v12.19.0, our CI matrix covers both versions. closes #365
1 parent 39bdc3f commit d466ab6

File tree

6 files changed

+535
-212
lines changed

6 files changed

+535
-212
lines changed

lib/binding.js

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const Directory = require('./directory.js');
77
const SymbolicLink = require('./symlink.js');
88
const {FSError} = require('./error.js');
99
const constants = require('constants');
10-
const getPathParts = require('./filesystem.js').getPathParts;
10+
const {getPathParts, getRealPath} = require('./filesystem.js');
1111

1212
const MODE_TO_KTYPE = {
1313
[constants.S_IFREG]: constants.UV_DIRENT_FILE,
@@ -260,10 +260,8 @@ Binding.prototype.realpath = function (filepath, encoding, callback, ctx) {
260260
throw new FSError('ENOENT', filepath);
261261
}
262262

263-
if (process.platform === 'win32' && realPath.startsWith('\\\\?\\')) {
264-
// Remove win32 file namespace prefix \\?\
265-
realPath = realPath.slice(4);
266-
}
263+
// Remove win32 file namespace prefix \\?\
264+
realPath = getRealPath(realPath);
267265

268266
if (encoding === 'buffer') {
269267
realPath = Buffer.from(realPath);
@@ -1073,12 +1071,45 @@ Binding.prototype.unlink = function (pathname, callback, ctx) {
10731071
Binding.prototype.utimes = function (pathname, atime, mtime, callback, ctx) {
10741072
markSyscall(ctx, 'utimes');
10751073

1074+
return maybeCallback(normalizeCallback(callback), ctx, this, function () {
1075+
let filepath = deBuffer(pathname);
1076+
let item = this._system.getItem(filepath);
1077+
let links = 0;
1078+
while (item instanceof SymbolicLink) {
1079+
if (links > MAX_LINKS) {
1080+
throw new FSError('ELOOP', filepath);
1081+
}
1082+
filepath = path.resolve(path.dirname(filepath), item.getPath());
1083+
item = this._system.getItem(filepath);
1084+
++links;
1085+
}
1086+
if (!item) {
1087+
throw new FSError('ENOENT', pathname);
1088+
}
1089+
item.setATime(new Date(atime * 1000));
1090+
item.setMTime(new Date(mtime * 1000));
1091+
});
1092+
};
1093+
1094+
/**
1095+
* Update timestamps.
1096+
* @param {string} pathname Path to item.
1097+
* @param {number} atime Access time (in seconds).
1098+
* @param {number} mtime Modification time (in seconds).
1099+
* @param {function(Error)} callback Optional callback.
1100+
* @param {object} ctx Context object (optional), only for nodejs v10+.
1101+
* @return {*} The return if no callback is provided.
1102+
*/
1103+
Binding.prototype.lutimes = function (pathname, atime, mtime, callback, ctx) {
1104+
markSyscall(ctx, 'utimes');
1105+
10761106
return maybeCallback(normalizeCallback(callback), ctx, this, function () {
10771107
pathname = deBuffer(pathname);
10781108
const item = this._system.getItem(pathname);
10791109
if (!item) {
10801110
throw new FSError('ENOENT', pathname);
10811111
}
1112+
// lutimes doesn't follow symlink
10821113
item.setATime(new Date(atime * 1000));
10831114
item.setMTime(new Date(mtime * 1000));
10841115
});
@@ -1098,7 +1129,17 @@ Binding.prototype.futimes = function (fd, atime, mtime, callback, ctx) {
10981129

10991130
return maybeCallback(normalizeCallback(callback), ctx, this, function () {
11001131
const descriptor = this.getDescriptorById(fd);
1101-
const item = descriptor.getItem();
1132+
let item = descriptor.getItem();
1133+
let filepath = this._system.getFilepath(item);
1134+
let links = 0;
1135+
while (item instanceof SymbolicLink) {
1136+
if (links > MAX_LINKS) {
1137+
throw new FSError('ELOOP', filepath);
1138+
}
1139+
filepath = path.resolve(path.dirname(filepath), item.getPath());
1140+
item = this._system.getItem(filepath);
1141+
++links;
1142+
}
11021143
item.setATime(new Date(atime * 1000));
11031144
item.setMTime(new Date(mtime * 1000));
11041145
});

lib/filesystem.js

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,26 @@ const SymbolicLink = require('./symlink.js');
99

1010
const isWindows = process.platform === 'win32';
1111

12-
function toNamespacedPath(filePath) {
13-
return path.toNamespacedPath
14-
? path.toNamespacedPath(filePath)
15-
: path._makeLong(filePath);
12+
// on Win32, change filepath from \\?\c:\a\b to C:\a\b
13+
function getRealPath(filepath) {
14+
if (isWindows && filepath.startsWith('\\\\?\\')) {
15+
// Remove win32 file namespace prefix \\?\
16+
return filepath[4].toUpperCase() + filepath.slice(5);
17+
}
18+
return filepath;
1619
}
1720

1821
function getPathParts(filepath) {
19-
const parts = toNamespacedPath(path.resolve(filepath)).split(path.sep);
22+
// path.toNamespacedPath is only for Win32 system.
23+
// on other platform, it returns the path unmodified.
24+
const parts = path.toNamespacedPath(path.resolve(filepath)).split(path.sep);
2025
parts.shift();
2126
if (isWindows) {
2227
// parts currently looks like ['', '?', 'c:', ...]
2328
parts.shift();
2429
const q = parts.shift(); // should be '?'
30+
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#win32-file-namespaces
31+
// Win32 File Namespaces prefix \\?\
2532
const base = '\\\\' + q + '\\' + parts.shift().toLowerCase();
2633
parts.unshift(base);
2734
}
@@ -130,6 +137,35 @@ FileSystem.prototype.getItem = function (filepath) {
130137
return item;
131138
};
132139

140+
function _getFilepath(item, itemPath, wanted) {
141+
if (item === wanted) {
142+
return itemPath;
143+
}
144+
if (item instanceof Directory) {
145+
for (const name of item.list()) {
146+
const got = _getFilepath(
147+
item.getItem(name),
148+
path.join(itemPath, name),
149+
wanted
150+
);
151+
if (got) {
152+
return got;
153+
}
154+
}
155+
}
156+
return null;
157+
}
158+
159+
/**
160+
* Get file path from a file system item.
161+
* @param {Item} item a file system item.
162+
* @return {string} file path for the item (or null if not found).
163+
*/
164+
FileSystem.prototype.getFilepath = function (item) {
165+
const namespacedPath = _getFilepath(this._root, isWindows ? '' : '/', item);
166+
return getRealPath(namespacedPath);
167+
};
168+
133169
/**
134170
* Populate a directory with an item.
135171
* @param {Directory} directory The directory to populate.
@@ -329,4 +365,4 @@ FileSystem.directory = function (config) {
329365
module.exports = FileSystem;
330366
exports = module.exports;
331367
exports.getPathParts = getPathParts;
332-
exports.toNamespacedPath = toNamespacedPath;
368+
exports.getRealPath = getRealPath;

lib/index.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ const {
1313
} = require('./readfilecontext.js');
1414
const fs = require('fs');
1515

16-
const toNamespacedPath = FileSystem.toNamespacedPath;
17-
1816
const realProcessProps = {
1917
cwd: process.cwd,
2018
chdir: process.chdir,
@@ -153,7 +151,7 @@ module.exports = function mock(config, options = {}) {
153151
},
154152
function chdir(directory) {
155153
if (realBinding._mockedBinding) {
156-
if (!fs.statSync(toNamespacedPath(directory)).isDirectory()) {
154+
if (!fs.statSync(path.toNamespacedPath(directory)).isDirectory()) {
157155
throw new FSError('ENOTDIR');
158156
}
159157
currentPath = path.resolve(currentPath, directory);

test/lib/filesystem.spec.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe('FileSystem', function () {
4040
});
4141
});
4242

43-
describe('#getItem()', function () {
43+
describe('#getItem() #getFilepath()', function () {
4444
it('gets an item', function () {
4545
const system = FileSystem.create({
4646
'one/two/three.js': 'contents',
@@ -49,6 +49,7 @@ describe('FileSystem', function () {
4949
const filepath = path.join('one', 'two', 'three.js');
5050
const item = system.getItem(filepath);
5151
assert.instanceOf(item, File);
52+
assert.equal(system.getFilepath(item), path.resolve(filepath));
5253
});
5354

5455
it('returns null if not found', function () {
@@ -79,10 +80,18 @@ describe('FileSystem', function () {
7980
});
8081
const file = system.getItem(path.join('dir-link', 'a'));
8182
assert.instanceOf(file, File);
83+
assert.equal(
84+
system.getFilepath(file),
85+
path.resolve(path.join('b', 'c', 'dir', 'a'))
86+
);
8287

8388
const dir = system.getItem(path.join('dir-link', 'b'));
8489
assert.instanceOf(dir, Directory);
8590
assert.deepEqual(dir.list().sort(), ['c', 'd']);
91+
assert.equal(
92+
system.getFilepath(dir),
93+
path.resolve(path.join('b', 'c', 'dir', 'b'))
94+
);
8695
});
8796
});
8897
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy