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 #144

Merged
merged 2 commits into from
Feb 26, 2016
Merged
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
143 changes: 115 additions & 28 deletions lib/dest/writeContents/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
'use strict';

var fs = require('fs');
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see #103 and #134

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@phated Not sure what you mean... If you meant dropping the zero, so it would leave parseInt('777', 8) -- that would do the exact same thing, since it is the same number...

I added the leading zero to emphasize we are now actively ignoring any sticky/setuid/setgid bits that users might have set on stat.mode. Notice that this does not set those bits on extant files on disk to 0 -- all this mask does is ignore any bits on stat.mode that are not rwx flags in building the argument to fchmod. And the TODO comment questioned if we wanted to allow users to set those other bits as well.

Either way, it is separate from the issue you linked, although if we were to allow these bits to be set by the user, and reflected on disk by dest, then there should be a test for it which doesn't use the realMode function of course (awkward description of what it does, acually).



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


function writeContents(writePath, file, cb) {
// If directory then mkdirp it
if (file.isDirectory()) {
Expand All @@ -29,49 +41,124 @@ function writeContents(writePath, file, cb) {

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

function complete(err) {
cb(err, file);
}

function written(err) {
// 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);
}

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

if (!file.stat || typeof file.stat.mode !== 'number' || file.symlink) {
return complete();
if (typeof fd === 'number') {
return stat(fd, close);
}

fs.stat(writePath, function(err, st) {
// No file descriptor given (writeDir or writeSymbolicLink) so create one.
fs.open(writePath, 'r', function(err, fd) {
if (err) {
return complete(err);
return close(err, finish);
}
var currentMode = (st.mode & parseInt('0777', 8));
var expectedMode = (file.stat.mode & parseInt('0777', 8));
if (currentMode === expectedMode) {
return complete();
}
fs.chmod(writePath, expectedMode, complete);

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

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

if (err.code === 'EEXIST' && file.flag === 'wx') {
// Handle scenario for file overwrite failures.
return false; // "These aren't the droids you're looking for"
}
// 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the ^ operator? I've never encountered it before

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, I guess I betrayed my C origins there. It's a nice operator to get bit-difference.

}

// 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);
});
}

// Otherwise, this is a fatal error
return true;
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);
});
}
});
}


// This is invoked by the close callbacks; we're all done.
function finish(err) {
cb(err, file);
}



}

module.exports = writeContents;
24 changes: 8 additions & 16 deletions lib/dest/writeContents/writeBuffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,21 @@

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

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

function writeBuffer(writePath, file, cb) {
function writeBuffer(writePath, file, written) {
var stat = file.stat;

fs.open(writePath, file.flag, stat.mode, function(err, fd) {
if (err) {
return cb(err);
return written(err);
}

fs.write(fd, file.contents, 0, file.contents.length, 0, function(error) {
if (error) {
return complete(error);
}
futimes(fd, stat, complete);
});

// Cleanup
function complete(err1) {
fs.close(fd, function(err2) {
cb(err1 || err2);
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);
});
});
}
});
});
}

Expand Down
4 changes: 2 additions & 2 deletions lib/dest/writeContents/writeDir.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

var mkdirp = require('mkdirp');

function writeDir(writePath, file, cb) {
mkdirp(writePath, file.stat.mode, cb);
function writeDir(writePath, file, written) {
mkdirp(writePath, file.stat.mode, written);
}

module.exports = writeDir;
33 changes: 19 additions & 14 deletions lib/dest/writeContents/writeStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@
var fs = require('graceful-fs');

var streamFile = require('../../src/getContents/streamFile');
var futimes = require('../../futimes');

function writeStream(writePath, file, cb) {
function writeStream(writePath, file, written) {
var stat = file.stat;
var mode = stat.mode;

var outStream;
var outFD;

fs.open(writePath, 'w', file.stat.mode, function(err, fd) {
fs.open(writePath, 'w', mode, function(err, fd) {
if (err) {
cb(err);
written(err);
}

var opt = {
mode: mode,
flag: file.flag,
autoClose: false,
fd: fd,
};

outFD = fd;
outStream = fs.createWriteStream(null, { fd: fd });
outStream = fs.createWriteStream(null, opt);

file.contents.once('error', complete);
file.contents.once('end', readStreamEnd);
Expand All @@ -35,14 +43,7 @@ function writeStream(writePath, file, cb) {
return complete(error);
}

futimes(outFD, stat, function(error) {
if (error) {
return complete(error);
}

// All finished with WriteStream, close and clean up
outStream.end();
});
complete();
});
}

Expand All @@ -54,7 +55,11 @@ function writeStream(writePath, file, cb) {
outStream.removeListener('error', complete);
outStream.removeListener('finish', complete);
}
cb(err);
written(err, outFD, function(err1, finish) {
outStream.end(function(err2) {
finish(err1 || err2);
});
});
}
}

Expand Down
4 changes: 2 additions & 2 deletions lib/dest/writeContents/writeSymbolicLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

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

function writeSymbolicLink(writePath, file, cb) {
function writeSymbolicLink(writePath, file, written) {
fs.symlink(file.symlink, writePath, function(err) {
if (err && err.code !== 'EEXIST') {
return cb(err);
}

cb(null, file);
written();
});
}

Expand Down
27 changes: 0 additions & 27 deletions lib/futimes.js

This file was deleted.

Loading