Skip to content

Commit 4345185

Browse files
trivikraduh95
authored andcommitted
vfs: make lchown update symlink metadata
Route mounted lchown operations through VFS lchown handling and add memory provider support that does not follow the final symlink. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: openai:gpt-5.5 PR-URL: #64573Fixes: #64572 Reviewed-By: Aviv Keller <me@aviv.sh> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 3bc0ee0 commit 4345185

6 files changed

Lines changed: 75 additions & 2 deletions

File tree

‎doc/api/vfs.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ signatures as their [`node:fs`][] counterparts:
156156
*`linkSync(existingPath, newPath)`
157157
*`chmodSync(path, mode)`
158158
*`chownSync(path, uid, gid)`
159+
*`lchownSync(path, uid, gid)`
159160
*`utimesSync(path, atime, mtime)`
160161
*`lutimesSync(path, atime, mtime)`
161162
*`mkdtempSync(prefix)`

‎lib/internal/vfs/file_system.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,11 @@ class VirtualFileSystem {
506506
this[kProvider].chownSync(providerPath,uid,gid);
507507
}
508508

509+
lchownSync(filePath,uid,gid){
510+
constproviderPath=this.#toProviderPath(filePath);
511+
this[kProvider].lchownSync(providerPath,uid,gid);
512+
}
513+
509514
utimesSync(filePath,atime,mtime){
510515
constproviderPath=this.#toProviderPath(filePath);
511516
this[kProvider].utimesSync(providerPath,atime,mtime);
@@ -1234,7 +1239,7 @@ class VirtualFileSystem {
12341239

12351240
asynclchown(filePath,uid,gid){
12361241
constproviderPath=toProviderPath(filePath);
1237-
provider.chownSync(providerPath,uid,gid);
1242+
provider.lchownSync(providerPath,uid,gid);
12381243
},
12391244

12401245
asyncutimes(filePath,atime,mtime){

‎lib/internal/vfs/provider.js‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,18 @@ class VirtualProvider {
235235
thrownewERR_METHOD_NOT_IMPLEMENTED('renameSync');
236236
}
237237

238+
/**
239+
* Changes ownership of a path without following the final symbolic link.
240+
* Providers with symlink support should override this.
241+
* @param {string} path The path
242+
* @param {number} uid The user id
243+
* @param {number} gid The group id
244+
* @returns {void}
245+
*/
246+
lchownSync(path,uid,gid){
247+
returnthis.chownSync(path,uid,gid);
248+
}
249+
238250
// === DEFAULT IMPLEMENTATIONS (built on primitives) ===
239251

240252
/**

‎lib/internal/vfs/providers/memory.js‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,13 @@ class MemoryProvider extends VirtualProvider {
986986
entry.ctime=DateNow();
987987
}
988988

989+
lchownSync(path,uid,gid){
990+
constentry=this.#getEntry(path,'chown',false);
991+
if(uid>=0)entry.uid=uid;
992+
if(gid>=0)entry.gid=gid;
993+
entry.ctime=DateNow();
994+
}
995+
989996
utimesSync(path,atime,mtime){
990997
constentry=this.#getEntry(path,'utime',true);
991998
entry.atime=toMs(atime);

‎lib/internal/vfs/setup.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ function createVfsHandlers() {
343343
vfsOpVoid(path,(vfs,n)=>vfs.symlinkSync(target,n,type)),
344344
chmodSync: (path,mode)=>vfsOpVoid(path,(vfs,n)=>vfs.chmodSync(n,mode)),
345345
chownSync: (path,uid,gid)=>vfsOpVoid(path,(vfs,n)=>vfs.chownSync(n,uid,gid)),
346-
lchownSync: (path,uid,gid)=>vfsOpVoid(path,(vfs,n)=>vfs.chownSync(n,uid,gid)),
346+
lchownSync: (path,uid,gid)=>vfsOpVoid(path,(vfs,n)=>vfs.lchownSync(n,uid,gid)),
347347
utimesSync: (path,atime,mtime)=>
348348
vfsOpVoid(path,(vfs,n)=>vfs.utimesSync(n,atime,mtime)),
349349
lutimesSync: (path,atime,mtime)=>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Flags: --experimental-vfs
2+
'use strict';
3+
4+
constcommon=require('../common');
5+
constassert=require('assert');
6+
constfs=require('fs');
7+
constfsp=require('fs/promises');
8+
constpath=require('path');
9+
constvfs=require('node:vfs');
10+
11+
constmountPoint=path.resolve('/tmp/vfs-lchown-'+process.pid);
12+
constmyVfs=vfs.create();
13+
myVfs.writeFileSync('/sync-target.txt','target');
14+
myVfs.symlinkSync('/sync-target.txt','/sync-link.txt');
15+
myVfs.writeFileSync('/async-target.txt','target');
16+
myVfs.symlinkSync('/async-target.txt','/async-link.txt');
17+
myVfs.writeFileSync('/promise-target.txt','target');
18+
myVfs.symlinkSync('/promise-target.txt','/promise-link.txt');
19+
myVfs.mount(mountPoint);
20+
21+
functionassertOwnership(filePath,uid,gid){
22+
conststats=fs.lstatSync(path.join(mountPoint,filePath));
23+
assert.strictEqual(stats.uid,uid);
24+
assert.strictEqual(stats.gid,gid);
25+
}
26+
27+
(async()=>{
28+
try{
29+
fs.lchownSync(path.join(mountPoint,'sync-link.txt'),123,456);
30+
assertOwnership('/sync-target.txt',0,0);
31+
assertOwnership('/sync-link.txt',123,456);
32+
33+
awaitnewPromise((resolve,reject)=>{
34+
fs.lchown(path.join(mountPoint,'async-link.txt'),234,567,(err)=>{
35+
if(err)reject(err);
36+
elseresolve();
37+
});
38+
});
39+
assertOwnership('/async-target.txt',0,0);
40+
assertOwnership('/async-link.txt',234,567);
41+
42+
awaitfsp.lchown(path.join(mountPoint,'promise-link.txt'),345,678);
43+
assertOwnership('/promise-target.txt',0,0);
44+
assertOwnership('/promise-link.txt',345,678);
45+
}finally{
46+
myVfs.unmount();
47+
}
48+
})().then(common.mustCall());

0 commit comments

Comments
 (0)