123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- 'use strict';
- var util = require('util');
- var fs = require('graceful-fs');
- var assign = require('object.assign');
- var date = require('value-or-function').date;
- var Writable = require('readable-stream').Writable;
- var constants = require('./constants');
- var APPEND_MODE_REGEXP = /a/;
- function closeFd(propagatedErr, fd, callback) {
- if (typeof fd !== 'number') {
- return callback(propagatedErr);
- }
- fs.close(fd, onClosed);
- function onClosed(closeErr) {
- if (propagatedErr || closeErr) {
- return callback(propagatedErr || closeErr);
- }
- callback();
- }
- }
- function isValidUnixId(id) {
- if (typeof id !== 'number') {
- return false;
- }
- if (id < 0) {
- return false;
- }
- return true;
- }
- function getFlags(options) {
- var flags = !options.append ? 'w' : 'a';
- if (!options.overwrite) {
- flags += 'x';
- }
- return flags;
- }
- function isFatalOverwriteError(err, flags) {
- if (!err) {
- return false;
- }
- if (err.code === 'EEXIST' && flags[1] === 'x') {
- // Handle scenario for file overwrite failures.
- return false;
- }
- // Otherwise, this is a fatal error
- return true;
- }
- function isFatalUnlinkError(err) {
- if (!err || err.code === 'ENOENT') {
- return false;
- }
- return true;
- }
- function getModeDiff(fsMode, vinylMode) {
- var modeDiff = 0;
- if (typeof vinylMode === 'number') {
- modeDiff = (vinylMode ^ fsMode) & constants.MASK_MODE;
- }
- return modeDiff;
- }
- function getTimesDiff(fsStat, vinylStat) {
- var mtime = date(vinylStat.mtime) || 0;
- if (!mtime) {
- return;
- }
- var atime = date(vinylStat.atime) || 0;
- if (+mtime === +fsStat.mtime &&
- +atime === +fsStat.atime) {
- return;
- }
- if (!atime) {
- atime = date(fsStat.atime) || undefined;
- }
- var timesDiff = {
- mtime: vinylStat.mtime,
- atime: atime,
- };
- 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 (uid === fsStat.uid &&
- 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');
- // If we don't have either, assume we don't have permissions.
- // This should only happen on Windows.
- // Windows basically noops fchmod and errors on futimes called on directories.
- if (!hasGeteuid && !hasGetuid) {
- return false;
- }
- var uid;
- if (hasGeteuid) {
- uid = process.geteuid();
- } else {
- uid = process.getuid();
- }
- if (fsStat.uid !== uid && uid !== 0) {
- return false;
- }
- return true;
- }
- function reflectStat(path, file, callback) {
- // Set file.stat to the reflect current state on disk
- fs.stat(path, onStat);
- function onStat(statErr, stat) {
- if (statErr) {
- return callback(statErr);
- }
- file.stat = stat;
- callback();
- }
- }
- function reflectLinkStat(path, file, callback) {
- // Set file.stat to the reflect current state on disk
- fs.lstat(path, onLstat);
- function onLstat(lstatErr, stat) {
- if (lstatErr) {
- return callback(lstatErr);
- }
- file.stat = stat;
- callback();
- }
- }
- function updateMetadata(fd, file, callback) {
- fs.fstat(fd, onStat);
- function onStat(statErr, stat) {
- if (statErr) {
- return callback(statErr);
- }
- // Check if mode needs to be updated
- var modeDiff = getModeDiff(stat.mode, file.stat.mode);
- // 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 && !ownerDiff) {
- return callback();
- }
- // Check access, `futimes`, `fchmod` & `fchown` only work if we own
- // the file, or if we are effectively root (`fchown` only when root).
- if (!isOwner(stat)) {
- return callback();
- }
- if (modeDiff) {
- return mode();
- }
- if (timesDiff) {
- return times();
- }
- owner();
- function mode() {
- var mode = stat.mode ^ modeDiff;
- fs.fchmod(fd, mode, onFchmod);
- function onFchmod(fchmodErr) {
- if (!fchmodErr) {
- file.stat.mode = mode;
- }
- if (timesDiff) {
- return times(fchmodErr);
- }
- if (ownerDiff) {
- return owner(fchmodErr);
- }
- callback(fchmodErr);
- }
- }
- function times(propagatedErr) {
- fs.futimes(fd, timesDiff.atime, timesDiff.mtime, onFutimes);
- function onFutimes(futimesErr) {
- if (!futimesErr) {
- file.stat.atime = timesDiff.atime;
- file.stat.mtime = timesDiff.mtime;
- }
- if (ownerDiff) {
- return owner(propagatedErr || futimesErr);
- }
- callback(propagatedErr || futimesErr);
- }
- }
- function owner(propagatedErr) {
- fs.fchown(fd, ownerDiff.uid, ownerDiff.gid, onFchown);
- function onFchown(fchownErr) {
- if (!fchownErr) {
- file.stat.uid = ownerDiff.uid;
- file.stat.gid = ownerDiff.gid;
- }
- callback(propagatedErr || fchownErr);
- }
- }
- }
- }
- function symlink(srcPath, destPath, opts, callback) {
- // Because fs.symlink does not allow atomic overwrite option with flags, we
- // delete and recreate if the link already exists and overwrite is true.
- if (opts.flags === 'w') {
- // TODO What happens when we call unlink with windows junctions?
- fs.unlink(destPath, onUnlink);
- } else {
- fs.symlink(srcPath, destPath, opts.type, onSymlink);
- }
- function onUnlink(unlinkErr) {
- if (isFatalUnlinkError(unlinkErr)) {
- return callback(unlinkErr);
- }
- fs.symlink(srcPath, destPath, opts.type, onSymlink);
- }
- function onSymlink(symlinkErr) {
- if (isFatalOverwriteError(symlinkErr, opts.flags)) {
- return callback(symlinkErr);
- }
- callback();
- }
- }
- /*
- Custom writeFile implementation because we need access to the
- file descriptor after the write is complete.
- Most of the implementation taken from node core.
- */
- function writeFile(filepath, data, options, callback) {
- if (typeof options === 'function') {
- callback = options;
- options = {};
- }
- if (!Buffer.isBuffer(data)) {
- return callback(new TypeError('Data must be a Buffer'));
- }
- if (!options) {
- options = {};
- }
- // Default the same as node
- var mode = options.mode || constants.DEFAULT_FILE_MODE;
- var flags = options.flags || 'w';
- var position = APPEND_MODE_REGEXP.test(flags) ? null : 0;
- fs.open(filepath, flags, mode, onOpen);
- function onOpen(openErr, fd) {
- if (openErr) {
- return onComplete(openErr);
- }
- fs.write(fd, data, 0, data.length, position, onComplete);
- function onComplete(writeErr) {
- callback(writeErr, fd);
- }
- }
- }
- function createWriteStream(path, options, flush) {
- return new WriteStream(path, options, flush);
- }
- // Taken from node core and altered to receive a flush function and simplified
- // To be used for cleanup (like updating times/mode/etc)
- function WriteStream(path, options, flush) {
- // Not exposed so we can avoid the case where someone doesn't use `new`
- if (typeof options === 'function') {
- flush = options;
- options = null;
- }
- options = options || {};
- Writable.call(this, options);
- this.flush = flush;
- this.path = path;
- this.mode = options.mode || constants.DEFAULT_FILE_MODE;
- this.flags = options.flags || 'w';
- // Used by node's `fs.WriteStream`
- this.fd = null;
- this.start = null;
- this.open();
- // Dispose on finish.
- this.once('finish', this.close);
- }
- util.inherits(WriteStream, Writable);
- WriteStream.prototype.open = function() {
- var self = this;
- fs.open(this.path, this.flags, this.mode, onOpen);
- function onOpen(openErr, fd) {
- if (openErr) {
- self.destroy();
- self.emit('error', openErr);
- return;
- }
- self.fd = fd;
- self.emit('open', fd);
- }
- };
- // Use our `end` method since it is patched for flush
- WriteStream.prototype.destroySoon = WriteStream.prototype.end;
- WriteStream.prototype._destroy = function(err, cb) {
- this.close(function(err2) {
- cb(err || err2);
- });
- };
- WriteStream.prototype.close = function(cb) {
- var that = this;
- if (cb) {
- this.once('close', cb);
- }
- if (this.closed || typeof this.fd !== 'number') {
- if (typeof this.fd !== 'number') {
- this.once('open', closeOnOpen);
- return;
- }
- return process.nextTick(function() {
- that.emit('close');
- });
- }
- this.closed = true;
- fs.close(this.fd, function(er) {
- if (er) {
- that.emit('error', er);
- } else {
- that.emit('close');
- }
- });
- this.fd = null;
- };
- WriteStream.prototype._final = function(callback) {
- if (typeof this.flush !== 'function') {
- return callback();
- }
- this.flush(this.fd, callback);
- };
- function closeOnOpen() {
- this.close();
- }
- WriteStream.prototype._write = function(data, encoding, callback) {
- var self = this;
- // This is from node core but I have no idea how to get code coverage on it
- if (!Buffer.isBuffer(data)) {
- return this.emit('error', new Error('Invalid data'));
- }
- if (typeof this.fd !== 'number') {
- return this.once('open', onOpen);
- }
- fs.write(this.fd, data, 0, data.length, null, onWrite);
- function onOpen() {
- self._write(data, encoding, callback);
- }
- function onWrite(writeErr) {
- if (writeErr) {
- self.destroy();
- callback(writeErr);
- return;
- }
- callback();
- }
- };
- module.exports = {
- closeFd: closeFd,
- isValidUnixId: isValidUnixId,
- getFlags: getFlags,
- isFatalOverwriteError: isFatalOverwriteError,
- isFatalUnlinkError: isFatalUnlinkError,
- getModeDiff: getModeDiff,
- getTimesDiff: getTimesDiff,
- getOwnerDiff: getOwnerDiff,
- isOwner: isOwner,
- reflectStat: reflectStat,
- reflectLinkStat: reflectLinkStat,
- updateMetadata: updateMetadata,
- symlink: symlink,
- writeFile: writeFile,
- createWriteStream: createWriteStream,
- };
|