Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit 22ff88a

Browse files
On Windows, patch Node's lstat to work around permissions error. Fixes #1101
1 parent 70d89b9 commit 22ff88a

File tree

3 files changed

+115
-13
lines changed

3 files changed

+115
-13
lines changed

src/Microsoft.AspNetCore.NodeServices/Content/Node/entrypoint-http.js

+66-13
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@
5555
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
5656
// but simplifies things for the consumer of this module.
5757
__webpack_require__(2);
58-
var http = __webpack_require__(3);
59-
var path = __webpack_require__(4);
60-
var ArgsUtil_1 = __webpack_require__(5);
61-
var ExitWhenParentExits_1 = __webpack_require__(6);
58+
__webpack_require__(4);
59+
var http = __webpack_require__(5);
60+
var path = __webpack_require__(3);
61+
var ArgsUtil_1 = __webpack_require__(6);
62+
var ExitWhenParentExits_1 = __webpack_require__(7);
6263
// Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct
6364
// reference to Node's runtime 'require' function.
6465
var dynamicRequire = eval('require');
@@ -142,6 +143,64 @@
142143

143144
/***/ },
144145
/* 2 */
146+
/***/ function(module, exports, __webpack_require__) {
147+
148+
"use strict";
149+
var path = __webpack_require__(3);
150+
var startsWith = function (str, prefix) { return str.substring(0, prefix.length) === prefix; };
151+
var appRootDir = process.cwd();
152+
function patchedLStat(pathToStatLong) {
153+
try {
154+
// If the lstat completes without errors, we don't modify its behavior at all
155+
return origLStat.apply(this, arguments);
156+
}
157+
catch (ex) {
158+
var shouldOverrideError = startsWith(ex.message, 'EPERM') // It's a permissions error
159+
&& typeof appRootDirLong === 'string'
160+
&& startsWith(appRootDirLong, pathToStatLong) // ... for an ancestor directory
161+
&& ex.stack.indexOf('Object.realpathSync ') >= 0; // ... during symlink resolution
162+
if (shouldOverrideError) {
163+
// Fake the result to give the same result as an 'lstat' on the app root dir.
164+
// This stops Node failing to load modules just because it doesn't know whether
165+
// ancestor directories are symlinks or not. If there's a genuine file
166+
// permissions issue, it will still surface later when Node actually
167+
// tries to read the file.
168+
return origLStat.call(this, appRootDir);
169+
}
170+
else {
171+
// In any other case, preserve the original error
172+
throw ex;
173+
}
174+
}
175+
}
176+
;
177+
// It's only necessary to apply this workaround on Windows
178+
var appRootDirLong = null;
179+
var origLStat = null;
180+
if (/^win/.test(process.platform)) {
181+
try {
182+
// Get the app's root dir in Node's internal "long" format (e.g., \\?\C:\dir\subdir)
183+
appRootDirLong = path._makeLong(appRootDir);
184+
// Actually apply the patch, being as defensive as possible
185+
var bindingFs = process.binding('fs');
186+
origLStat = bindingFs.lstat;
187+
if (typeof origLStat === 'function') {
188+
bindingFs.lstat = patchedLStat;
189+
}
190+
}
191+
catch (ex) {
192+
}
193+
}
194+
195+
196+
/***/ },
197+
/* 3 */
198+
/***/ function(module, exports) {
199+
200+
module.exports = require("path");
201+
202+
/***/ },
203+
/* 4 */
145204
/***/ function(module, exports) {
146205

147206
// When Node writes to stdout/strerr, we capture that and convert the lines into calls on the
@@ -182,19 +241,13 @@
182241

183242

184243
/***/ },
185-
/* 3 */
244+
/* 5 */
186245
/***/ function(module, exports) {
187246

188247
module.exports = require("http");
189248

190249
/***/ },
191-
/* 4 */
192-
/***/ function(module, exports) {
193-
194-
module.exports = require("path");
195-
196-
/***/ },
197-
/* 5 */
250+
/* 6 */
198251
/***/ function(module, exports) {
199252

200253
"use strict";
@@ -220,7 +273,7 @@
220273

221274

222275
/***/ },
223-
/* 6 */
276+
/* 7 */
224277
/***/ function(module, exports) {
225278

226279
/*

src/Microsoft.AspNetCore.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive,
22
// but simplifies things for the consumer of this module.
3+
import './Util/PatchModuleResolutionLStat';
34
import './Util/OverrideStdOutputs';
45
import * as http from 'http';
56
import * as path from 'path';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as path from 'path';
2+
const startsWith = (str: string, prefix: string) => str.substring(0, prefix.length) === prefix;
3+
const appRootDir = process.cwd();
4+
5+
function patchedLStat(pathToStatLong: string) {
6+
try {
7+
// If the lstat completes without errors, we don't modify its behavior at all
8+
return origLStat.apply(this, arguments);
9+
} catch(ex) {
10+
const shouldOverrideError =
11+
startsWith(ex.message, 'EPERM') // It's a permissions error
12+
&& typeof appRootDirLong === 'string'
13+
&& startsWith(appRootDirLong, pathToStatLong) // ... for an ancestor directory
14+
&& ex.stack.indexOf('Object.realpathSync ') >= 0; // ... during symlink resolution
15+
16+
if (shouldOverrideError) {
17+
// Fake the result to give the same result as an 'lstat' on the app root dir.
18+
// This stops Node failing to load modules just because it doesn't know whether
19+
// ancestor directories are symlinks or not. If there's a genuine file
20+
// permissions issue, it will still surface later when Node actually
21+
// tries to read the file.
22+
return origLStat.call(this, appRootDir);
23+
} else {
24+
// In any other case, preserve the original error
25+
throw ex;
26+
}
27+
}
28+
};
29+
30+
// It's only necessary to apply this workaround on Windows
31+
let appRootDirLong: string = null;
32+
let origLStat: Function = null;
33+
if (/^win/.test(process.platform)) {
34+
try {
35+
// Get the app's root dir in Node's internal "long" format (e.g., \\?\C:\dir\subdir)
36+
appRootDirLong = (path as any)._makeLong(appRootDir);
37+
38+
// Actually apply the patch, being as defensive as possible
39+
const bindingFs = (process as any).binding('fs');
40+
origLStat = bindingFs.lstat;
41+
if (typeof origLStat === 'function') {
42+
bindingFs.lstat = patchedLStat;
43+
}
44+
} catch(ex) {
45+
// If some future version of Node throws (e.g., to prevent use of process.binding()),
46+
// don't apply the patch, but still let the application run.
47+
}
48+
}

0 commit comments

Comments
 (0)