From f5287299ae67d490531b7e6ce57fd448d28d2a70 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 23 Sep 2016 14:24:13 +0200 Subject: [PATCH] =?UTF-8?q?Always=20make=20`fs.Stats`=E2=80=99=20`.uid`=20?= =?UTF-8?q?and=20`.gid`=20fields=20unsigned?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At the time of writing, all currently published versions of Node.js return signed 32-bit integers in their return values for the `uid` and `gid` fields of `fs.Stats` instances. This is problematic, because some of Node’s other `fs` methods like `chown` expect unsigned 32-bit integer input and throw when encountering negative integers; this has broken e.g. `sudo npm install -g` on `OS X`, where `nobody` has a UID that would be returned as `-2` by `fs.stat()`. Ref: https://github.com/nodejs/node/pull/8515 Ref: https://github.com/npm/npm/issues/13918 --- polyfills.js | 34 ++++++++++++++++++++++++++++++++++ test/stats-uid-gid.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 test/stats-uid-gid.js diff --git a/polyfills.js b/polyfills.js index 2798050..39d8176 100644 --- a/polyfills.js +++ b/polyfills.js @@ -56,6 +56,14 @@ function patch (fs) { fs.fchmodSync = chmodFixSync(fs.fchmodSync) fs.lchmodSync = chmodFixSync(fs.lchmodSync) + fs.stat = statFix(fs.stat) + fs.fstat = statFix(fs.fstat) + fs.lstat = statFix(fs.lstat) + + fs.statSync = statFixSync(fs.statSync) + fs.fstatSync = statFixSync(fs.fstatSync) + fs.lstatSync = statFixSync(fs.lstatSync) + // if lchmod/lchown do not exist, then make them no-ops if (!fs.lchmod) { fs.lchmod = function (path, mode, cb) { @@ -246,6 +254,32 @@ function chownFixSync (orig) { } } + +function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, cb) { + return orig.call(fs, target, function (er, stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + if (cb) cb.apply(this, arguments) + }) + } +} + +function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target) { + var stats = orig.call(fs, target) + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + return stats; + } +} + // ENOSYS means that the fs doesn't support the op. Just ignore // that, because it doesn't matter. // diff --git a/test/stats-uid-gid.js b/test/stats-uid-gid.js new file mode 100644 index 0000000..e3476f7 --- /dev/null +++ b/test/stats-uid-gid.js @@ -0,0 +1,29 @@ +'use strict'; +var test = require('tap').test +var util = require('util') +var fs = require('fs') + +// mock fs.statSync to return signed uids/gids +var realStatSync = fs.statSync +fs.statSync = function(path) { + var stats = realStatSync.call(fs, path) + stats.uid = -2 + stats.gid = -2 + return stats +} + +var gfs = require('../graceful-fs.js') + +test('graceful fs uses same stats constructor as fs', function (t) { + t.equal(gfs.Stats, fs.Stats, 'should reference the same constructor') + + if (!process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) { + t.equal(fs.statSync(__filename).uid, -2) + t.equal(fs.statSync(__filename).gid, -2) + } + + t.equal(gfs.statSync(__filename).uid, 0xfffffffe) + t.equal(gfs.statSync(__filename).gid, 0xfffffffe) + + t.end() +})