Skip to content

Commit

Permalink
Merge pull request #366 from tschaub/dev-lutimes
Browse files Browse the repository at this point in the history
fix: fix bug on utimes, futimes, add support of lutimes
  • Loading branch information
tschaub committed Sep 23, 2022
2 parents 39bdc3f + d466ab6 commit faaa676
Show file tree
Hide file tree
Showing 6 changed files with 535 additions and 212 deletions.
53 changes: 47 additions & 6 deletions lib/binding.js
Expand Up @@ -7,7 +7,7 @@ const Directory = require('./directory.js');
const SymbolicLink = require('./symlink.js');
const {FSError} = require('./error.js');
const constants = require('constants');
const getPathParts = require('./filesystem.js').getPathParts;
const {getPathParts, getRealPath} = require('./filesystem.js');

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

if (process.platform === 'win32' && realPath.startsWith('\\\\?\\')) {
// Remove win32 file namespace prefix \\?\
realPath = realPath.slice(4);
}
// Remove win32 file namespace prefix \\?\
realPath = getRealPath(realPath);

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

return maybeCallback(normalizeCallback(callback), ctx, this, function () {
let filepath = deBuffer(pathname);
let item = this._system.getItem(filepath);
let links = 0;
while (item instanceof SymbolicLink) {
if (links > MAX_LINKS) {
throw new FSError('ELOOP', filepath);
}
filepath = path.resolve(path.dirname(filepath), item.getPath());
item = this._system.getItem(filepath);
++links;
}
if (!item) {
throw new FSError('ENOENT', pathname);
}
item.setATime(new Date(atime * 1000));
item.setMTime(new Date(mtime * 1000));
});
};

/**
* Update timestamps.
* @param {string} pathname Path to item.
* @param {number} atime Access time (in seconds).
* @param {number} mtime Modification time (in seconds).
* @param {function(Error)} callback Optional callback.
* @param {object} ctx Context object (optional), only for nodejs v10+.
* @return {*} The return if no callback is provided.
*/
Binding.prototype.lutimes = function (pathname, atime, mtime, callback, ctx) {
markSyscall(ctx, 'utimes');

return maybeCallback(normalizeCallback(callback), ctx, this, function () {
pathname = deBuffer(pathname);
const item = this._system.getItem(pathname);
if (!item) {
throw new FSError('ENOENT', pathname);
}
// lutimes doesn't follow symlink
item.setATime(new Date(atime * 1000));
item.setMTime(new Date(mtime * 1000));
});
Expand All @@ -1098,7 +1129,17 @@ Binding.prototype.futimes = function (fd, atime, mtime, callback, ctx) {

return maybeCallback(normalizeCallback(callback), ctx, this, function () {
const descriptor = this.getDescriptorById(fd);
const item = descriptor.getItem();
let item = descriptor.getItem();
let filepath = this._system.getFilepath(item);
let links = 0;
while (item instanceof SymbolicLink) {
if (links > MAX_LINKS) {
throw new FSError('ELOOP', filepath);
}
filepath = path.resolve(path.dirname(filepath), item.getPath());
item = this._system.getItem(filepath);
++links;
}
item.setATime(new Date(atime * 1000));
item.setMTime(new Date(mtime * 1000));
});
Expand Down
48 changes: 42 additions & 6 deletions lib/filesystem.js
Expand Up @@ -9,19 +9,26 @@ const SymbolicLink = require('./symlink.js');

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

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

function getPathParts(filepath) {
const parts = toNamespacedPath(path.resolve(filepath)).split(path.sep);
// path.toNamespacedPath is only for Win32 system.
// on other platform, it returns the path unmodified.
const parts = path.toNamespacedPath(path.resolve(filepath)).split(path.sep);
parts.shift();
if (isWindows) {
// parts currently looks like ['', '?', 'c:', ...]
parts.shift();
const q = parts.shift(); // should be '?'
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#win32-file-namespaces
// Win32 File Namespaces prefix \\?\
const base = '\\\\' + q + '\\' + parts.shift().toLowerCase();
parts.unshift(base);
}
Expand Down Expand Up @@ -130,6 +137,35 @@ FileSystem.prototype.getItem = function (filepath) {
return item;
};

function _getFilepath(item, itemPath, wanted) {
if (item === wanted) {
return itemPath;
}
if (item instanceof Directory) {
for (const name of item.list()) {
const got = _getFilepath(
item.getItem(name),
path.join(itemPath, name),
wanted
);
if (got) {
return got;
}
}
}
return null;
}

/**
* Get file path from a file system item.
* @param {Item} item a file system item.
* @return {string} file path for the item (or null if not found).
*/
FileSystem.prototype.getFilepath = function (item) {
const namespacedPath = _getFilepath(this._root, isWindows ? '' : '/', item);
return getRealPath(namespacedPath);
};

/**
* Populate a directory with an item.
* @param {Directory} directory The directory to populate.
Expand Down Expand Up @@ -329,4 +365,4 @@ FileSystem.directory = function (config) {
module.exports = FileSystem;
exports = module.exports;
exports.getPathParts = getPathParts;
exports.toNamespacedPath = toNamespacedPath;
exports.getRealPath = getRealPath;
4 changes: 1 addition & 3 deletions lib/index.js
Expand Up @@ -13,8 +13,6 @@ const {
} = require('./readfilecontext.js');
const fs = require('fs');

const toNamespacedPath = FileSystem.toNamespacedPath;

const realProcessProps = {
cwd: process.cwd,
chdir: process.chdir,
Expand Down Expand Up @@ -153,7 +151,7 @@ module.exports = function mock(config, options = {}) {
},
function chdir(directory) {
if (realBinding._mockedBinding) {
if (!fs.statSync(toNamespacedPath(directory)).isDirectory()) {
if (!fs.statSync(path.toNamespacedPath(directory)).isDirectory()) {
throw new FSError('ENOTDIR');
}
currentPath = path.resolve(currentPath, directory);
Expand Down
11 changes: 10 additions & 1 deletion test/lib/filesystem.spec.js
Expand Up @@ -40,7 +40,7 @@ describe('FileSystem', function () {
});
});

describe('#getItem()', function () {
describe('#getItem() #getFilepath()', function () {
it('gets an item', function () {
const system = FileSystem.create({
'one/two/three.js': 'contents',
Expand All @@ -49,6 +49,7 @@ describe('FileSystem', function () {
const filepath = path.join('one', 'two', 'three.js');
const item = system.getItem(filepath);
assert.instanceOf(item, File);
assert.equal(system.getFilepath(item), path.resolve(filepath));
});

it('returns null if not found', function () {
Expand Down Expand Up @@ -79,10 +80,18 @@ describe('FileSystem', function () {
});
const file = system.getItem(path.join('dir-link', 'a'));
assert.instanceOf(file, File);
assert.equal(
system.getFilepath(file),
path.resolve(path.join('b', 'c', 'dir', 'a'))
);

const dir = system.getItem(path.join('dir-link', 'b'));
assert.instanceOf(dir, Directory);
assert.deepEqual(dir.list().sort(), ['c', 'd']);
assert.equal(
system.getFilepath(dir),
path.resolve(path.join('b', 'c', 'dir', 'b'))
);
});
});
});
Expand Down

0 comments on commit faaa676

Please sign in to comment.