Skip to content

Commit

Permalink
Breaking: Added support for changing uid/gid on disk (closes #157) (#188
Browse files Browse the repository at this point in the history
)
  • Loading branch information
sleeuwen authored and phated committed Nov 28, 2017
1 parent 5814e90 commit a6552b1
Show file tree
Hide file tree
Showing 3 changed files with 394 additions and 2 deletions.
76 changes: 74 additions & 2 deletions lib/file-operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ function closeFd(propagatedErr, fd, callback) {
}
}

function isValidUnixId(id) {
if (typeof id !== 'number') {
return false;
}

if (id < 0) {
return false;
}

return true;
}

function getModeDiff(fsMode, vinylMode) {
var modeDiff = 0;

Expand Down Expand Up @@ -66,6 +78,40 @@ function getTimesDiff(fsStat, vinylStat) {
return timesDiff;
}

function getOwnerDiff(fsStat, vinylStat) {
if (!isValidUnixId(vinylStat.uid) &&
!isValidUnixId(vinylStat.gid)) {
return;
}

if ((!isValidUnixId(fsStat.uid) && !isValidUnixId(vinylStat.uid)) ||
(!isValidUnixId(fsStat.gid) && !isValidUnixId(vinylStat.gid))) {
return;
}

var uid = fsStat.uid; // Default to current uid.
if (isValidUnixId(vinylStat.uid)) {
uid = vinylStat.uid;
}

var gid = fsStat.gid; // Default to current gid.
if (isValidUnixId(vinylStat.gid)) {
gid = vinylStat.gid;
}

if (isEqual(uid, fsStat.uid) &&
isEqual(gid, fsStat.gid)) {
return;
}

var ownerDiff = {
uid: uid,
gid: gid,
};

return ownerDiff;
}

function isOwner(fsStat) {
var hasGetuid = (typeof process.getuid === 'function');
var hasGeteuid = (typeof process.geteuid === 'function');
Expand Down Expand Up @@ -106,11 +152,14 @@ function updateMetadata(fd, file, callback) {
// Check if atime/mtime need to be updated
var timesDiff = getTimesDiff(stat, file.stat);

// Check if uid/gid need to be updated
var ownerDiff = getOwnerDiff(stat, file.stat);

// Set file.stat to the reflect current state on disk
assign(file.stat, stat);

// Nothing to do
if (!modeDiff && !timesDiff) {
if (!modeDiff && !timesDiff && !ownerDiff) {
return callback(null);
}

Expand All @@ -123,7 +172,10 @@ function updateMetadata(fd, file, callback) {
if (modeDiff) {
return mode();
}
times();
if (timesDiff) {
return times();
}
owner();

function mode() {
var mode = stat.mode ^ modeDiff;
Expand All @@ -137,6 +189,9 @@ function updateMetadata(fd, file, callback) {
if (timesDiff) {
return times(fchmodErr);
}
if (ownerDiff) {
return owner(fchmodErr);
}
callback(fchmodErr);
}
}
Expand All @@ -149,9 +204,24 @@ function updateMetadata(fd, file, callback) {
file.stat.atime = timesDiff.atime;
file.stat.mtime = timesDiff.mtime;
}
if (ownerDiff) {
return owner(fchmodErr || futimesErr);
}
callback(fchmodErr || futimesErr);
}
}

function owner(earlierErr) {
fs.fchown(fd, ownerDiff.uid, ownerDiff.gid, onFchown);

function onFchown(fchownErr) {
if (!fchownErr) {
file.stat.uid = ownerDiff.uid;
file.stat.gid = ownerDiff.gid;
}
callback(earlierErr || fchownErr);
}
}
}
}

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

module.exports = {
closeFd: closeFd,
isValidUnixId: isValidUnixId,
getModeDiff: getModeDiff,
getTimesDiff: getTimesDiff,
getOwnerDiff: getOwnerDiff,
isOwner: isOwner,
updateMetadata: updateMetadata,
writeFile: writeFile,
Expand Down
99 changes: 99 additions & 0 deletions test/dest-owner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use strict';

var os = require('os');
var path = require('path');

var fs = require('graceful-fs');
var del = require('del');
var File = require('vinyl');
var expect = require('expect');

var vfs = require('../');

function wipeOut() {
this.timeout(20000);

expect.restoreSpies();

// Async del to get sort-of-fix for https://github.com/isaacs/rimraf/issues/72
return del(path.join(__dirname, './out-fixtures/'));
}

var isWindows = (os.platform() === 'win32');

describe('.dest() with custom owner', function() {
beforeEach(wipeOut);
afterEach(wipeOut);

it('should call fchown when the uid and/or gid are provided on the vinyl stat', function(done) {
if (isWindows) {
this.skip();
return;
}

var inputPath = path.join(__dirname, './fixtures/test.coffee');
var inputBase = path.join(__dirname, './fixtures/');
var expectedPath = path.join(__dirname, './out-fixtures/test.coffee');
var expectedContents = fs.readFileSync(inputPath);

var fchownSpy = expect.spyOn(fs, 'fchown').andCallThrough();

var expectedFile = new File({
base: inputBase,
cwd: __dirname,
path: inputPath,
contents: expectedContents,
stat: {
uid: 1001,
gid: 1001,
},
});

var onEnd = function() {
expect(fchownSpy.calls.length).toEqual(1);
expect(fchownSpy.calls[0].arguments[1]).toEqual(1001);
expect(fchownSpy.calls[0].arguments[2]).toEqual(1001);
done();
};

var stream = vfs.dest('./out-fixtures/', { cwd: __dirname });
stream.on('end', onEnd);
stream.write(expectedFile);
stream.end();
});

it('should not call fchown when the uid and gid provided on the vinyl stat are invalid', function(done) {
if (isWindows) {
this.skip();
return;
}

var inputPath = path.join(__dirname, './fixtures/test.coffee');
var inputBase = path.join(__dirname, './fixtures/');
var expectedPath = path.join(__dirname, './out-fixtures/test.coffee');
var expectedContents = fs.readFileSync(inputPath);

var fchownSpy = expect.spyOn(fs, 'fchown').andCallThrough();

var expectedFile = new File({
base: inputBase,
cwd: __dirname,
path: inputPath,
contents: expectedContents,
stat: {
uid: -1,
gid: -1,
},
});

var onEnd = function() {
expect(fchownSpy.calls.length).toEqual(0);
done();
};

var stream = vfs.dest('./out-fixtures/', { cwd: __dirname });
stream.on('end', onEnd);
stream.write(expectedFile);
stream.end();
});
});
Loading

0 comments on commit a6552b1

Please sign in to comment.