Skip to content

Commit 9fd1689

Browse files
sleeuwenphated
authored andcommitted
Breaking: Added support for changing uid/gid on disk (closes #157) (#188)
1 parent 69f2c75 commit 9fd1689

File tree

3 files changed

+394
-2
lines changed

3 files changed

+394
-2
lines changed

lib/file-operations.js

+74-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ function closeFd(propagatedErr, fd, callback) {
2626
}
2727
}
2828

29+
function isValidUnixId(id) {
30+
if (typeof id !== 'number') {
31+
return false;
32+
}
33+
34+
if (id < 0) {
35+
return false;
36+
}
37+
38+
return true;
39+
}
40+
2941
function getModeDiff(fsMode, vinylMode) {
3042
var modeDiff = 0;
3143

@@ -66,6 +78,40 @@ function getTimesDiff(fsStat, vinylStat) {
6678
return timesDiff;
6779
}
6880

81+
function getOwnerDiff(fsStat, vinylStat) {
82+
if (!isValidUnixId(vinylStat.uid) &&
83+
!isValidUnixId(vinylStat.gid)) {
84+
return;
85+
}
86+
87+
if ((!isValidUnixId(fsStat.uid) && !isValidUnixId(vinylStat.uid)) ||
88+
(!isValidUnixId(fsStat.gid) && !isValidUnixId(vinylStat.gid))) {
89+
return;
90+
}
91+
92+
var uid = fsStat.uid; // Default to current uid.
93+
if (isValidUnixId(vinylStat.uid)) {
94+
uid = vinylStat.uid;
95+
}
96+
97+
var gid = fsStat.gid; // Default to current gid.
98+
if (isValidUnixId(vinylStat.gid)) {
99+
gid = vinylStat.gid;
100+
}
101+
102+
if (isEqual(uid, fsStat.uid) &&
103+
isEqual(gid, fsStat.gid)) {
104+
return;
105+
}
106+
107+
var ownerDiff = {
108+
uid: uid,
109+
gid: gid,
110+
};
111+
112+
return ownerDiff;
113+
}
114+
69115
function isOwner(fsStat) {
70116
var hasGetuid = (typeof process.getuid === 'function');
71117
var hasGeteuid = (typeof process.geteuid === 'function');
@@ -106,11 +152,14 @@ function updateMetadata(fd, file, callback) {
106152
// Check if atime/mtime need to be updated
107153
var timesDiff = getTimesDiff(stat, file.stat);
108154

155+
// Check if uid/gid need to be updated
156+
var ownerDiff = getOwnerDiff(stat, file.stat);
157+
109158
// Set file.stat to the reflect current state on disk
110159
assign(file.stat, stat);
111160

112161
// Nothing to do
113-
if (!modeDiff && !timesDiff) {
162+
if (!modeDiff && !timesDiff && !ownerDiff) {
114163
return callback(null);
115164
}
116165

@@ -123,7 +172,10 @@ function updateMetadata(fd, file, callback) {
123172
if (modeDiff) {
124173
return mode();
125174
}
126-
times();
175+
if (timesDiff) {
176+
return times();
177+
}
178+
owner();
127179

128180
function mode() {
129181
var mode = stat.mode ^ modeDiff;
@@ -137,6 +189,9 @@ function updateMetadata(fd, file, callback) {
137189
if (timesDiff) {
138190
return times(fchmodErr);
139191
}
192+
if (ownerDiff) {
193+
return owner(fchmodErr);
194+
}
140195
callback(fchmodErr);
141196
}
142197
}
@@ -149,9 +204,24 @@ function updateMetadata(fd, file, callback) {
149204
file.stat.atime = timesDiff.atime;
150205
file.stat.mtime = timesDiff.mtime;
151206
}
207+
if (ownerDiff) {
208+
return owner(fchmodErr || futimesErr);
209+
}
152210
callback(fchmodErr || futimesErr);
153211
}
154212
}
213+
214+
function owner(earlierErr) {
215+
fs.fchown(fd, ownerDiff.uid, ownerDiff.gid, onFchown);
216+
217+
function onFchown(fchownErr) {
218+
if (!fchownErr) {
219+
file.stat.uid = ownerDiff.uid;
220+
file.stat.gid = ownerDiff.gid;
221+
}
222+
callback(earlierErr || fchownErr);
223+
}
224+
}
155225
}
156226
}
157227

@@ -257,8 +327,10 @@ function mkdirp(dirpath, customMode, callback) {
257327

258328
module.exports = {
259329
closeFd: closeFd,
330+
isValidUnixId: isValidUnixId,
260331
getModeDiff: getModeDiff,
261332
getTimesDiff: getTimesDiff,
333+
getOwnerDiff: getOwnerDiff,
262334
isOwner: isOwner,
263335
updateMetadata: updateMetadata,
264336
writeFile: writeFile,

test/dest-owner.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
'use strict';
2+
3+
var os = require('os');
4+
var path = require('path');
5+
6+
var fs = require('graceful-fs');
7+
var del = require('del');
8+
var File = require('vinyl');
9+
var expect = require('expect');
10+
11+
var vfs = require('../');
12+
13+
function wipeOut() {
14+
this.timeout(20000);
15+
16+
expect.restoreSpies();
17+
18+
// Async del to get sort-of-fix for https://github.com/isaacs/rimraf/issues/72
19+
return del(path.join(__dirname, './out-fixtures/'));
20+
}
21+
22+
var isWindows = (os.platform() === 'win32');
23+
24+
describe('.dest() with custom owner', function() {
25+
beforeEach(wipeOut);
26+
afterEach(wipeOut);
27+
28+
it('should call fchown when the uid and/or gid are provided on the vinyl stat', function(done) {
29+
if (isWindows) {
30+
this.skip();
31+
return;
32+
}
33+
34+
var inputPath = path.join(__dirname, './fixtures/test.coffee');
35+
var inputBase = path.join(__dirname, './fixtures/');
36+
var expectedPath = path.join(__dirname, './out-fixtures/test.coffee');
37+
var expectedContents = fs.readFileSync(inputPath);
38+
39+
var fchownSpy = expect.spyOn(fs, 'fchown').andCallThrough();
40+
41+
var expectedFile = new File({
42+
base: inputBase,
43+
cwd: __dirname,
44+
path: inputPath,
45+
contents: expectedContents,
46+
stat: {
47+
uid: 1001,
48+
gid: 1001,
49+
},
50+
});
51+
52+
var onEnd = function() {
53+
expect(fchownSpy.calls.length).toEqual(1);
54+
expect(fchownSpy.calls[0].arguments[1]).toEqual(1001);
55+
expect(fchownSpy.calls[0].arguments[2]).toEqual(1001);
56+
done();
57+
};
58+
59+
var stream = vfs.dest('./out-fixtures/', { cwd: __dirname });
60+
stream.on('end', onEnd);
61+
stream.write(expectedFile);
62+
stream.end();
63+
});
64+
65+
it('should not call fchown when the uid and gid provided on the vinyl stat are invalid', function(done) {
66+
if (isWindows) {
67+
this.skip();
68+
return;
69+
}
70+
71+
var inputPath = path.join(__dirname, './fixtures/test.coffee');
72+
var inputBase = path.join(__dirname, './fixtures/');
73+
var expectedPath = path.join(__dirname, './out-fixtures/test.coffee');
74+
var expectedContents = fs.readFileSync(inputPath);
75+
76+
var fchownSpy = expect.spyOn(fs, 'fchown').andCallThrough();
77+
78+
var expectedFile = new File({
79+
base: inputBase,
80+
cwd: __dirname,
81+
path: inputPath,
82+
contents: expectedContents,
83+
stat: {
84+
uid: -1,
85+
gid: -1,
86+
},
87+
});
88+
89+
var onEnd = function() {
90+
expect(fchownSpy.calls.length).toEqual(0);
91+
done();
92+
};
93+
94+
var stream = vfs.dest('./out-fixtures/', { cwd: __dirname });
95+
stream.on('end', onEnd);
96+
stream.write(expectedFile);
97+
stream.end();
98+
});
99+
});

0 commit comments

Comments
 (0)