diff --git a/doc/api/fs.md b/doc/api/fs.md index ebab0a600735ad..6ac840a76b9600 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -2368,6 +2368,38 @@ changes: Synchronous lchown(2). Returns `undefined`. +## `fs.lutimes(path, atime, mtime, callback)` +<!-- YAML +added: REPLACEME +--> + +* `path` {string|Buffer|URL} +* `atime` {number|string|Date} +* `mtime` {number|string|Date} +* `callback` {Function} + * `err` {Error} + +Changes the access and modification times of a file in the same way as +[`fs.utimes()`][], with the difference that if the path refers to a symbolic +link, then the link is not dereferenced: instead, the timestamps of the +symbolic link itself are changed. + +No arguments other than a possible exception are given to the completion +callback. + +## `fs.lutimesSync(path, atime, mtime)` +<!-- YAML +added: REPLACEME +--> + +* `path` {string|Buffer|URL} +* `atime` {number|string|Date} +* `mtime` {number|string|Date} + +Change the file system timestamps of the symbolic link referenced by `path`. +Returns `undefined`, or throws an exception when parameters are incorrect or +the operation fails. This is the synchronous version of [`fs.lutimes()`][]. + ## `fs.link(existingPath, newPath, callback)` <!-- YAML added: v0.1.31 @@ -4946,6 +4978,23 @@ changes: Changes the ownership on a symbolic link then resolves the `Promise` with no arguments upon success. +### `fsPromises.lutimes(path, atime, mtime)` +<!-- YAML +added: REPLACEME +--> + +* `path` {string|Buffer|URL} +* `atime` {number|string|Date} +* `mtime` {number|string|Date} +* Returns: {Promise} + +Changes the access and modification times of a file in the same way as +[`fsPromises.utimes()`][], with the difference that if the path refers to a +symbolic link, then the link is not dereferenced: instead, the timestamps of +the symbolic link itself are changed. + +Upon success, the `Promise` is resolved without arguments. + ### `fsPromises.link(existingPath, newPath)` <!-- YAML added: v10.0.0 @@ -5755,6 +5804,7 @@ the file contents. [`fs.ftruncate()`]: #fs_fs_ftruncate_fd_len_callback [`fs.futimes()`]: #fs_fs_futimes_fd_atime_mtime_callback [`fs.lstat()`]: #fs_fs_lstat_path_options_callback +[`fs.lutimes()`]: #fs_fs_lutimes_path_atime_mtime_callback [`fs.mkdir()`]: #fs_fs_mkdir_path_options_callback [`fs.mkdtemp()`]: #fs_fs_mkdtemp_prefix_options_callback [`fs.open()`]: #fs_fs_open_path_flags_mode_callback @@ -5778,6 +5828,7 @@ the file contents. [`fs.writev()`]: #fs_fs_writev_fd_buffers_position_callback [`fsPromises.open()`]: #fs_fspromises_open_path_flags_mode [`fsPromises.opendir()`]: #fs_fspromises_opendir_path_options +[`fsPromises.utimes()`]: #fs_fspromises_utimes_path_atime_mtime [`inotify(7)`]: http://man7.org/linux/man-pages/man7/inotify.7.html [`kqueue(2)`]: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2 [`net.Socket`]: net.html#net_class_net_socket diff --git a/lib/fs.js b/lib/fs.js index 9b70b237ef00e1..03b5451c4c1123 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -1303,6 +1303,28 @@ function futimesSync(fd, atime, mtime) { handleErrorFromBinding(ctx); } +function lutimes(path, atime, mtime, callback) { + callback = makeCallback(callback); + path = getValidatedPath(path); + + const req = new FSReqCallback(); + req.oncomplete = callback; + binding.lutimes(pathModule.toNamespacedPath(path), + toUnixTimestamp(atime), + toUnixTimestamp(mtime), + req); +} + +function lutimesSync(path, atime, mtime) { + path = getValidatedPath(path); + const ctx = { path }; + binding.lutimes(pathModule.toNamespacedPath(path), + toUnixTimestamp(atime), + toUnixTimestamp(mtime), + undefined, ctx); + handleErrorFromBinding(ctx); +} + function writeAll(fd, isUserFd, buffer, offset, length, callback) { // write(fd, buffer, offset, length, position, callback) fs.write(fd, buffer, offset, length, null, (writeErr, written) => { @@ -1938,6 +1960,8 @@ module.exports = fs = { linkSync, lstat, lstatSync, + lutimes, + lutimesSync, mkdir, mkdirSync, mkdtemp, diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 31eaeef2846216..515ac20dfa5b8c 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -474,6 +474,14 @@ async function futimes(handle, atime, mtime) { return binding.futimes(handle.fd, atime, mtime, kUsePromises); } +async function lutimes(path, atime, mtime) { + path = getValidatedPath(path); + return binding.lutimes(pathModule.toNamespacedPath(path), + toUnixTimestamp(atime), + toUnixTimestamp(mtime), + kUsePromises); +} + async function realpath(path, options) { options = getOptions(options, {}); path = getValidatedPath(path); @@ -541,6 +549,7 @@ module.exports = { lchown, chown, utimes, + lutimes, realpath, mkdtemp, writeFile, diff --git a/src/node_file.cc b/src/node_file.cc index 0cce86e8f36dc3..ecf49dead7cdaa 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -2247,6 +2247,35 @@ static void FUTimes(const FunctionCallbackInfo<Value>& args) { } } +static void LUTimes(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + const int argc = args.Length(); + CHECK_GE(argc, 3); + + BufferValue path(env->isolate(), args[0]); + CHECK_NOT_NULL(*path); + + CHECK(args[1]->IsNumber()); + const double atime = args[1].As<Number>()->Value(); + + CHECK(args[2]->IsNumber()); + const double mtime = args[2].As<Number>()->Value(); + + FSReqBase* req_wrap_async = GetReqWrap(env, args[3]); + if (req_wrap_async != nullptr) { // lutimes(path, atime, mtime, req) + AsyncCall(env, req_wrap_async, args, "lutime", UTF8, AfterNoArgs, + uv_fs_lutime, *path, atime, mtime); + } else { // lutimes(path, atime, mtime, undefined, ctx) + CHECK_EQ(argc, 5); + FSReqWrapSync req_wrap_sync; + FS_SYNC_TRACE_BEGIN(lutimes); + SyncCall(env, args[4], &req_wrap_sync, "lutime", + uv_fs_lutime, *path, atime, mtime); + FS_SYNC_TRACE_END(lutimes); + } +} + static void Mkdtemp(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); @@ -2329,6 +2358,7 @@ void Initialize(Local<Object> target, env->SetMethod(target, "utimes", UTimes); env->SetMethod(target, "futimes", FUTimes); + env->SetMethod(target, "lutimes", LUTimes); env->SetMethod(target, "mkdtemp", Mkdtemp); diff --git a/test/parallel/test-fs-utimes.js b/test/parallel/test-fs-utimes.js index b72d263cf6ce50..b81c5b6bf62940 100644 --- a/test/parallel/test-fs-utimes.js +++ b/test/parallel/test-fs-utimes.js @@ -24,13 +24,17 @@ const common = require('../common'); const assert = require('assert'); const util = require('util'); const fs = require('fs'); +const url = require('url'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); -function stat_resource(resource) { +const lpath = `${tmpdir.path}/symlink`; +fs.symlinkSync('unoent-entry', lpath); + +function stat_resource(resource, statSync = fs.statSync) { if (typeof resource === 'string') { - return fs.statSync(resource); + return statSync(resource); } const stats = fs.fstatSync(resource); // Ensure mtime has been written to disk @@ -41,9 +45,9 @@ function stat_resource(resource) { return fs.fstatSync(resource); } -function check_mtime(resource, mtime) { +function check_mtime(resource, mtime, statSync) { mtime = fs._toUnixTimestamp(mtime); - const stats = stat_resource(resource); + const stats = stat_resource(resource, statSync); const real_mtime = fs._toUnixTimestamp(stats.mtime); return mtime - real_mtime; } @@ -55,8 +59,8 @@ function expect_errno(syscall, resource, err, errno) { ); } -function expect_ok(syscall, resource, err, atime, mtime) { - const mtime_diff = check_mtime(resource, mtime); +function expect_ok(syscall, resource, err, atime, mtime, statSync) { + const mtime_diff = check_mtime(resource, mtime, statSync); assert( // Check up to single-second precision. // Sub-second precision is OS and fs dependant. @@ -68,45 +72,55 @@ function expect_ok(syscall, resource, err, atime, mtime) { const stats = fs.statSync(tmpdir.path); +const asPath = (path) => path; +const asUrl = (path) => url.pathToFileURL(path); + const cases = [ - new Date('1982-09-10 13:37'), - new Date(), - 123456.789, - stats.mtime, - ['123456', -1], - new Date('2017-04-08T17:59:38.008Z') + [asPath, new Date('1982-09-10 13:37')], + [asPath, new Date()], + [asPath, 123456.789], + [asPath, stats.mtime], + [asPath, '123456', -1], + [asPath, new Date('2017-04-08T17:59:38.008Z')], + [asUrl, new Date()], ]; + runTests(cases.values()); function runTests(iter) { const { value, done } = iter.next(); if (done) return; + // Support easy setting same or different atime / mtime values. - const [atime, mtime] = Array.isArray(value) ? value : [value, value]; + const [pathType, atime, mtime = atime] = value; let fd; // // test async code paths // - fs.utimes(tmpdir.path, atime, mtime, common.mustCall((err) => { + fs.utimes(pathType(tmpdir.path), atime, mtime, common.mustCall((err) => { expect_ok('utimes', tmpdir.path, err, atime, mtime); - fs.utimes('foobarbaz', atime, mtime, common.mustCall((err) => { - expect_errno('utimes', 'foobarbaz', err, 'ENOENT'); + fs.lutimes(pathType(lpath), atime, mtime, common.mustCall((err) => { + expect_ok('lutimes', lpath, err, atime, mtime, fs.lstatSync); + + fs.utimes(pathType('foobarbaz'), atime, mtime, common.mustCall((err) => { + expect_errno('utimes', 'foobarbaz', err, 'ENOENT'); - // don't close this fd - if (common.isWindows) { - fd = fs.openSync(tmpdir.path, 'r+'); - } else { - fd = fs.openSync(tmpdir.path, 'r'); - } + // don't close this fd + if (common.isWindows) { + fd = fs.openSync(tmpdir.path, 'r+'); + } else { + fd = fs.openSync(tmpdir.path, 'r'); + } - fs.futimes(fd, atime, mtime, common.mustCall((err) => { - expect_ok('futimes', fd, err, atime, mtime); + fs.futimes(fd, atime, mtime, common.mustCall((err) => { + expect_ok('futimes', fd, err, atime, mtime); - syncTests(); + syncTests(); - setImmediate(common.mustCall(runTests), iter); + setImmediate(common.mustCall(runTests), iter); + })); })); })); })); @@ -115,9 +129,12 @@ function runTests(iter) { // test synchronized code paths, these functions throw on failure // function syncTests() { - fs.utimesSync(tmpdir.path, atime, mtime); + fs.utimesSync(pathType(tmpdir.path), atime, mtime); expect_ok('utimesSync', tmpdir.path, undefined, atime, mtime); + fs.lutimesSync(pathType(lpath), atime, mtime); + expect_ok('lutimesSync', lpath, undefined, atime, mtime, fs.lstatSync); + // Some systems don't have futimes // if there's an error, it should be ENOSYS try { @@ -129,7 +146,7 @@ function runTests(iter) { let err; try { - fs.utimesSync('foobarbaz', atime, mtime); + fs.utimesSync(pathType('foobarbaz'), atime, mtime); } catch (ex) { err = ex; }