Skip to content

Commit 22ff88a

Browse files
On Windows, patch Node's lstat to work around permissions error. Fixesaspnet#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‎

Lines changed: 66 additions & 13 deletions
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-
varhttp=__webpack_require__(3);
59-
varpath=__webpack_require__(4);
60-
varArgsUtil_1=__webpack_require__(5);
61-
varExitWhenParentExits_1=__webpack_require__(6);
58+
__webpack_require__(4);
59+
varhttp=__webpack_require__(5);
60+
varpath=__webpack_require__(3);
61+
varArgsUtil_1=__webpack_require__(6);
62+
varExitWhenParentExits_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
vardynamicRequire=eval('require');
@@ -142,6 +143,64 @@
142143

143144
/***/},
144145
/* 2 */
146+
/***/function(module,exports,__webpack_require__){
147+
148+
"use strict";
149+
varpath=__webpack_require__(3);
150+
varstartsWith=function(str,prefix){returnstr.substring(0,prefix.length)===prefix;};
151+
varappRootDir=process.cwd();
152+
functionpatchedLStat(pathToStatLong){
153+
try{
154+
// If the lstat completes without errors, we don't modify its behavior at all
155+
returnorigLStat.apply(this,arguments);
156+
}
157+
catch(ex){
158+
varshouldOverrideError=startsWith(ex.message,'EPERM')// It's a permissions error
159+
&&typeofappRootDirLong==='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+
returnorigLStat.call(this,appRootDir);
169+
}
170+
else{
171+
// In any other case, preserve the original error
172+
throwex;
173+
}
174+
}
175+
}
176+
;
177+
// It's only necessary to apply this workaround on Windows
178+
varappRootDirLong=null;
179+
varorigLStat=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+
varbindingFs=process.binding('fs');
186+
origLStat=bindingFs.lstat;
187+
if(typeoforigLStat==='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‎

Lines changed: 1 addition & 0 deletions
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*ashttpfrom'http';
56
import*aspathfrom'path';
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import*aspathfrom'path';
2+
conststartsWith=(str: string,prefix: string)=>str.substring(0,prefix.length)===prefix;
3+
constappRootDir=process.cwd();
4+
5+
functionpatchedLStat(pathToStatLong: string){
6+
try{
7+
// If the lstat completes without errors, we don't modify its behavior at all
8+
returnorigLStat.apply(this,arguments);
9+
}catch(ex){
10+
constshouldOverrideError=
11+
startsWith(ex.message,'EPERM')// It's a permissions error
12+
&&typeofappRootDirLong==='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+
returnorigLStat.call(this,appRootDir);
23+
}else{
24+
// In any other case, preserve the original error
25+
throwex;
26+
}
27+
}
28+
};
29+
30+
// It's only necessary to apply this workaround on Windows
31+
letappRootDirLong: string=null;
32+
letorigLStat: 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=(pathasany)._makeLong(appRootDir);
37+
38+
// Actually apply the patch, being as defensive as possible
39+
constbindingFs=(processasany).binding('fs');
40+
origLStat=bindingFs.lstat;
41+
if(typeoforigLStat==='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)