Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature metadata refactor #1

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
sudo: false
sudo: required
language: node_js
node_js:
- 'stable'
- '4.1'
- '4.0'
- '4'
- '0.12'
- '0.10'
before_script:
- find test -type d -exec chmod g+s {} \;
- sudo chown root test/not-owned/
- sudo chown root test/not-owned/not-owned.txt
after_script:
- npm run coveralls
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,16 @@ Any through2-related options are documented in [through2].
Takes a folder path string or a function as the first argument and an options object as the second. If given a function, it will be called with each [vinyl] `File` object and must return a folder path.
Returns a stream that accepts [vinyl] `File` objects, writes them to disk at the folder/cwd specified, and passes them downstream so you can keep piping these around.

Once the file is written to disk, an attempt is made to determine if the `stat.mode`, `stat.mtime` and `stat.atime` of the [vinyl] `File` object differ from the file on the filesystem.
If they differ and the running process owns the file, the corresponding filesystem metadata is updated.
If they don't differ or the process doesn't own the file, the attempt is skipped silently.
__This functionality is disabled on Windows operating systems or any other OS that doesn't support `process.getuid` or `process.geteuid` in node. This is due to Windows having very unexpected results through usage of `fs.fchmod` and `fs.futimes`.__

If the file has a `symlink` attribute specifying a target path, then a symlink will be created.

__Note: The file will be modified after being written to this stream.__
- `cwd`, `base`, and `path` will be overwritten to match the folder.
- `stat.mode` will be overwritten if you used a mode parameter.
- `stat` will be updated to match the file on the filesystem.
- `contents` will have it's position reset to the beginning if it is a stream.

#### Options
Expand Down
24 changes: 24 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# http://www.appveyor.com/docs/appveyor-yml
# http://www.appveyor.com/docs/lang/nodejs-iojs

environment:
matrix:
# node.js
- nodejs_version: "0.10"
- nodejs_version: "0.12"
- nodejs_version: "4"
- nodejs_version: "5"

install:
- ps: Install-Product node $env:nodejs_version
- npm install

test_script:
- node --version
- npm --version
- cmd: npm test

build: off

# build version format
version: "{build}"
2 changes: 1 addition & 1 deletion lib/dest/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function dest(outFolder, opt) {

var sourcemapOpt = opt.sourcemaps;
if (typeof sourcemapOpt === 'boolean') {
sourcemapOpt = { sourcemaps: sourcemapOpt };
sourcemapOpt = {};
}

var mapStream = sourcemaps.write(sourcemapOpt.path, sourcemapOpt);
Expand Down
137 changes: 16 additions & 121 deletions lib/dest/writeContents/index.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
'use strict';

var fs = require('graceful-fs');
var assign = require('object-assign');

var writeDir = require('./writeDir');
var writeStream = require('./writeStream');
var writeBuffer = require('./writeBuffer');
var writeSymbolicLink = require('./writeSymbolicLink');

// TODO include sticky/setuid/setgid, i.e. 7777?
var MASK_MODE = parseInt('0777', 8);


// http://stackoverflow.com/a/10589791/586382
function validDate(date) {
return date instanceof Date && !isNaN(date.valueOf());
}


function writeContents(writePath, file, cb) {
function writeContents(writePath, file, callback) {
// If directory then mkdirp it
if (file.isDirectory()) {
return writeDir(writePath, file, written);
Expand All @@ -41,124 +28,32 @@ function writeContents(writePath, file, cb) {

// If no contents then do nothing
if (file.isNull()) {
return finish();
return written();
}


// This is invoked by the various writeXxx modules when they've finished
// writing the contents.
// The third argument, if present, should be invoked to indicate we're done
// with the file descriptor.
function written(err, fd, close) {
close = close || function(err, cb) {
cb(err);
};

if (err && !(err.code === 'EEXIST' && file.flag === 'wx')) {
return close(err, finish);
}

// TODO handle symlinks properly
if (!file.stat || file.symlink) {
return close(null, finish);
function written(err) {
if (isErrorFatal(err)) {
return callback(err);
}

if (typeof fd === 'number') {
return stat(fd, close);
}

// No file descriptor given (writeDir or writeSymbolicLink) so create one.
fs.open(writePath, 'r', function(err, fd) {
if (err) {
return close(err, finish);
}

stat(fd, function(err1) {
fs.close(fd, function(err2) {
finish(err1 || err2);
});
});
});
callback(null, file);
}

function isErrorFatal(err) {
if (!err) {
return false;
}

// Set mode and/or atime, mtime.
function stat(fd, close) {
fs.fstat(fd, function(err, stat) {
if (err) {
return close(err, finish);
}

// Check if mode needs to be updated
var modeDiff = 0;
if (typeof file.stat.mode === 'number') {
modeDiff = (file.stat.mode ^ stat.mode) & MASK_MODE;
}

// Check if atime/mtime need to be updated
var timesDiff = null;
if (validDate(file.stat.mtime)) {
timesDiff = {
mtime: file.stat.mtime,
atime: validDate(file.stat.atime) ? file.stat.atime : stat.atime,
};
}

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

// Nothing to do
if (!modeDiff && !timesDiff) {
return close(null, finish);
}

// Check access, `futimes` and `fchmod` only work if we own the file,
// or if we are effectively root.
var uid = process.geteuid ? process.geteuid() : process.getuid();
if (stat.uid !== uid && uid !== 0) {
return close(null, finish);
}

if (modeDiff) {
return mode();
}
times();

function mode() {
var mode = stat.mode ^ modeDiff;
fs.fchmod(fd, mode, function(err) {
if (!err) {
file.stat.mode = mode;
file.stat.ctime.setTime(Date.now());
}
if (timesDiff) {
return times(err);
}
close(err, finish);
});
}

function times(err1) {
fs.futimes(fd, timesDiff.atime, timesDiff.mtime, function(err2) {
if (!err2) {
file.stat.atime = timesDiff.atime;
file.stat.mtime = timesDiff.mtime;
file.stat.ctime.setTime(Date.now());
}
close(err1 || err2, finish);
});
}
});
}

if (err.code === 'EEXIST' && file.flag === 'wx') {
// Handle scenario for file overwrite failures.
return false; // "These aren't the droids you're looking for"
}

// This is invoked by the close callbacks; we're all done.
function finish(err) {
cb(err, file);
// Otherwise, this is a fatal error
return true;
}



}

module.exports = writeContents;
29 changes: 16 additions & 13 deletions lib/dest/writeContents/writeBuffer.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
'use strict';

var fs = require('graceful-fs');
var fo = require('../../fileOperations');

function writeBuffer(writePath, file, written) {
var stat = file.stat;
var opt = {
mode: file.stat.mode,
flag: file.flag,
};

fs.open(writePath, file.flag, stat.mode, function(err, fd) {
if (err) {
return written(err);
fo.writeFile(writePath, file.contents, opt, onWriteFile);

function onWriteFile(writeErr, fd) {
if (writeErr) {
return fo.closeFd(writeErr, fd, written);
}

fs.write(fd, file.contents, 0, file.contents.length, 0, function(err) {
written(err, fd, function(err1, finish) {
fs.close(fd, function(err2) {
finish(err1 || err2);
});
});
});
});
fo.updateMetadata(fd, file, onUpdate);
}

function onUpdate(statErr, fd) {
fo.closeFd(statErr, fd, written);
}
}

module.exports = writeBuffer;
46 changes: 45 additions & 1 deletion lib/dest/writeContents/writeDir.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,53 @@
'use strict';

var fs = require('graceful-fs');
var mkdirp = require('mkdirp');

var fo = require('../../fileOperations');

function writeDir(writePath, file, written) {
mkdirp(writePath, file.stat.mode, written);
var mkdirpOpts = {
mode: file.stat.mode,
fs: fs,
};
mkdirp(writePath, mkdirpOpts, onMkdirp);

function onMkdirp(mkdirpErr) {
if (mkdirpErr) {
return written(mkdirpErr);
}

fs.open(writePath, 'r', onOpen);
}

function onOpen(openErr, fd) {
// If we don't have access, just move along
if (isInaccessible(openErr)) {
return fo.closeFd(null, fd, written);
}

if (openErr) {
return fo.closeFd(openErr, fd, written);
}

fo.updateMetadata(fd, file, onUpdate);
}

function onUpdate(statErr, fd) {
fo.closeFd(statErr, fd, written);
}
}

function isInaccessible(err) {
if (!err) {
return false;
}

if (err.code === 'EACCES') {
return true;
}

return false;
}

module.exports = writeDir;
Loading