Skip to content

Commit 3e668cc

Browse files
committed
assure shim path points to DataRoot instead of ProgramRoot. Closes #1388
1 parent 35ed2b0 commit 3e668cc

1 file changed

Lines changed: 184 additions & 40 deletions

File tree

‎installer/setup.iss‎

Lines changed: 184 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,24 @@ begin
532532
Result := Trim(NodeStoragePage.Values[0]);
533533
end;
534534
535+
{ DataRoot is the parent of InstallRoot (.shim / .nodejs / .cache live here). }
536+
{ Default InstallRoot={app}\installs keeps DataRoot={app}. Custom storage splits them. }
537+
function GetDataRoot(Param: String): String;
538+
var
539+
InstallRoot: String;
540+
begin
541+
InstallRoot := NormalizePath(GetInstallRoot(''));
542+
if InstallRoot = '' then
543+
begin
544+
Result := ExpandConstant('{app}');
545+
Exit;
546+
end;
547+
548+
Result := NormalizePath(ExtractFileDir(InstallRoot));
549+
if Result = '' then
550+
Result := ExpandConstant('{app}');
551+
end;
552+
535553
function GetLegacySettingValue(const SettingsText: String; const Key: String): String;
536554
var
537555
Normalized: String;
@@ -1038,6 +1056,7 @@ var
10381056
NormalizedLegacyNodePath: String;
10391057
NormalizedCurrentHome: String;
10401058
NormalizedCurrentNodePath: String;
1059+
NormalizedProgramNodePath: String;
10411060
I: Integer;
10421061
RemoveLegacyHome: Boolean;
10431062
RemoveLegacyNodePath: Boolean;
@@ -1051,7 +1070,9 @@ begin
10511070
NormalizedLegacyHome := NormalizePath(Trim(LegacyInstallDir));
10521071
NormalizedLegacyNodePath := NormalizePath(Trim(LegacySettingsPath));
10531072
NormalizedCurrentHome := NormalizePath(ExpandConstant('{app}'));
1054-
NormalizedCurrentNodePath := NormalizePath(ExpandConstant('{app}\\.nodejs'));
1073+
{ PATH node entry must track DataRoot (.shim lives there), not always {app}. }
1074+
NormalizedCurrentNodePath := NormalizePath(AddBackslash(GetDataRoot('')) + '.nodejs');
1075+
NormalizedProgramNodePath := NormalizePath(ExpandConstant('{app}\\.nodejs'));
10551076
10561077
RemoveLegacyHome := RemoveLegacyEntries and (NormalizedLegacyHome <> '') and
10571078
(CompareText(NormalizedLegacyHome, NormalizedCurrentHome) <> 0);
@@ -1072,7 +1093,8 @@ begin
10721093
if (CompareText(Segment, '%NVM_HOME%') = 0) or
10731094
(CompareText(Segment, '%NVM_HOME%\\.nodejs') = 0) or
10741095
IsSameExpandedPath(Segment, NormalizedCurrentHome) or
1075-
IsSameExpandedPath(Segment, NormalizedCurrentNodePath) then
1096+
IsSameExpandedPath(Segment, NormalizedCurrentNodePath) or
1097+
IsSameExpandedPath(Segment, NormalizedProgramNodePath) then
10761098
Continue;
10771099
end;
10781100
@@ -1224,7 +1246,7 @@ begin
12241246
if LegacySymlinkPath = '' then
12251247
Exit;
12261248
1227-
NewNodePath := NormalizePath(ExpandConstant('{app}\\.nodejs'));
1249+
NewNodePath := NormalizePath(AddBackslash(GetDataRoot('')) + '.nodejs');
12281250
if CompareText(LegacySymlinkPath, NewNodePath) = 0 then
12291251
Exit;
12301252
@@ -1289,7 +1311,7 @@ begin
12891311
if (LegacySettingsPath <> '') and
12901312
not SameText(
12911313
RemoveBackslashUnlessRoot(Trim(LegacySettingsPath)),
1292-
RemoveBackslashUnlessRoot(ExpandConstant('{app}\.nodejs'))
1314+
RemoveBackslashUnlessRoot(AddBackslash(GetDataRoot('')) + '.nodejs')
12931315
) then
12941316
UpgraderArgs := UpgraderArgs + ' --symlink-path "' + LegacySettingsPath + '"';
12951317
@@ -2264,43 +2286,71 @@ end;
22642286
function PrepareToInstall(var NeedsRestart: Boolean): String;
22652287
var
22662288
ShimDir: String;
2289+
DataShimDir: String;
22672290
ResultCode: Integer;
22682291
UserGrant: String;
22692292
begin
22702293
Result := '';
22712294
NeedsRestart := False;
22722295
ShimDir := ExpandConstant('{app}\.shim');
2273-
if not DirExists(ShimDir) then
2274-
Exit;
2296+
DataShimDir := AddBackslash(GetDataRoot('')) + '.shim';
22752297
22762298
{ Runtime LockShimDirectory makes .shim a protected read-only DACL.
2277-
Community ProgramRoot == DataRoot, so reinstall extracts node.exe here
2278-
and Inno reports "create a file in the destination directory: Access is denied."
2279-
User still has WRITE_DAC, so icacls /reset works without elevation. }
2280-
if Exec(
2281-
ExpandConstant('{sys}\icacls.exe'),
2282-
'"' + ShimDir + '" /reset /T /C /Q',
2283-
'',
2284-
SW_HIDE,
2285-
ewWaitUntilTerminated,
2286-
ResultCode
2287-
) then
2288-
Log('PrepareToInstall: reset .shim ACL, icacls exit=' + IntToStr(ResultCode))
2289-
else
2290-
Log('PrepareToInstall: icacls /reset failed to start for ' + ShimDir);
2299+
When DataRoot == {app}, reinstall extracts node.exe here and Inno reports
2300+
"create a file in the destination directory: Access is denied."
2301+
User still has WRITE_DAC, so icacls /reset works without elevation.
2302+
When DataRoot differs, unlock both ProgramRoot and DataRoot shim dirs. }
2303+
if DirExists(ShimDir) then
2304+
begin
2305+
if Exec(
2306+
ExpandConstant('{sys}\icacls.exe'),
2307+
'"' + ShimDir + '" /reset /T /C /Q',
2308+
'',
2309+
SW_HIDE,
2310+
ewWaitUntilTerminated,
2311+
ResultCode
2312+
) then
2313+
Log('PrepareToInstall: reset .shim ACL, icacls exit=' + IntToStr(ResultCode))
2314+
else
2315+
Log('PrepareToInstall: icacls /reset failed to start for ' + ShimDir);
22912316
2292-
UserGrant := '"' + ShimDir + '" /grant:r "' + GetUserNameString + ':(OI)(CI)(F)" /T /C /Q';
2293-
if Exec(
2294-
ExpandConstant('{sys}\icacls.exe'),
2295-
UserGrant,
2296-
'',
2297-
SW_HIDE,
2298-
ewWaitUntilTerminated,
2299-
ResultCode
2300-
) then
2301-
Log('PrepareToInstall: grant .shim write, icacls exit=' + IntToStr(ResultCode))
2302-
else
2303-
Log('PrepareToInstall: icacls /grant failed to start for ' + ShimDir);
2317+
UserGrant := '"' + ShimDir + '" /grant:r "' + GetUserNameString + ':(OI)(CI)(F)" /T /C /Q';
2318+
if Exec(
2319+
ExpandConstant('{sys}\icacls.exe'),
2320+
UserGrant,
2321+
'',
2322+
SW_HIDE,
2323+
ewWaitUntilTerminated,
2324+
ResultCode
2325+
) then
2326+
Log('PrepareToInstall: grant .shim write, icacls exit=' + IntToStr(ResultCode))
2327+
else
2328+
Log('PrepareToInstall: icacls /grant failed to start for ' + ShimDir);
2329+
end;
2330+
2331+
if (CompareText(NormalizePath(ShimDir), NormalizePath(DataShimDir)) <> 0) and DirExists(DataShimDir) then
2332+
begin
2333+
if Exec(
2334+
ExpandConstant('{sys}\icacls.exe'),
2335+
'"' + DataShimDir + '" /reset /T /C /Q',
2336+
'',
2337+
SW_HIDE,
2338+
ewWaitUntilTerminated,
2339+
ResultCode
2340+
) then
2341+
Log('PrepareToInstall: reset DataRoot .shim ACL, icacls exit=' + IntToStr(ResultCode));
2342+
2343+
UserGrant := '"' + DataShimDir + '" /grant:r "' + GetUserNameString + ':(OI)(CI)(F)" /T /C /Q';
2344+
if Exec(
2345+
ExpandConstant('{sys}\icacls.exe'),
2346+
UserGrant,
2347+
'',
2348+
SW_HIDE,
2349+
ewWaitUntilTerminated,
2350+
ResultCode
2351+
) then
2352+
Log('PrepareToInstall: grant DataRoot .shim write, icacls exit=' + IntToStr(ResultCode));
2353+
end;
23042354
end;
23052355
23062356
procedure InitializeWizard;
@@ -2407,6 +2457,7 @@ begin
24072457
Result :=
24082458
'NVM for Windows will be installed with the following settings:' + NewLine + NewLine +
24092459
Space + 'Node.js storage: ' + GetInstallRoot('') + NewLine +
2460+
Space + 'Runtime data root: ' + GetDataRoot('') + NewLine +
24102461
Space + 'Operating mode: ' + Mode + NewLine + NewLine +
24112462
Space + 'Keep downloaded Node.js setup files (cache for reinstall): ';
24122463
if (PreferencesPage <> nil) and PreferencesPage.Values[0] then
@@ -2515,10 +2566,90 @@ begin
25152566
BroadcastEnvironmentChange();
25162567
end;
25172568
2569+
{ When InstallRoot is customized, DataRoot leaves {app}. Seed runtime .shim + proxy }
2570+
{ so PATH/.nodejs match where nvm use / reshim write shims (closes #1388). }
2571+
procedure EnsureDataRootRuntimeLayout();
2572+
var
2573+
DataRoot: String;
2574+
ProgramRoot: String;
2575+
ProgramShim: String;
2576+
DataShim: String;
2577+
ProgramProxy: String;
2578+
DataProxy: String;
2579+
ResultCode: Integer;
2580+
begin
2581+
DataRoot := NormalizePath(GetDataRoot(''));
2582+
ProgramRoot := NormalizePath(ExpandConstant('{app}'));
2583+
ProgramShim := AddBackslash(ProgramRoot) + '.shim';
2584+
DataShim := AddBackslash(DataRoot) + '.shim';
2585+
ProgramProxy := ExpandConstant('{app}\utils\proxy.exe');
2586+
DataProxy := AddBackslash(DataRoot) + 'proxy.exe';
2587+
2588+
AppendInstallLog('EnsureDataRootRuntimeLayout: ProgramRoot=' + ProgramRoot);
2589+
AppendInstallLog('EnsureDataRootRuntimeLayout: DataRoot=' + DataRoot);
2590+
2591+
if not ForceDirectories(DataShim) then
2592+
begin
2593+
AppendInstallLogWarn('EnsureDataRootRuntimeLayout: could not create ' + DataShim);
2594+
Exit;
2595+
end;
2596+
2597+
if CompareText(ProgramRoot, DataRoot) = 0 then
2598+
begin
2599+
AppendInstallLog('EnsureDataRootRuntimeLayout: DataRoot equals ProgramRoot, no seed copy needed');
2600+
Exit;
2601+
end;
2602+
2603+
if DirExists(ProgramShim) then
2604+
begin
2605+
if not Exec(
2606+
'robocopy.exe',
2607+
'"' + ProgramShim + '" "' + DataShim + '" /E /MT:8 /R:1 /W:1 /NP /NFL /NDL /NJH /NJS',
2608+
'',
2609+
SW_HIDE,
2610+
ewWaitUntilTerminated,
2611+
ResultCode
2612+
) then
2613+
AppendInstallLogWarn('EnsureDataRootRuntimeLayout: robocopy seed failed to start')
2614+
else if ResultCode >= 8 then
2615+
AppendInstallLogWarn('EnsureDataRootRuntimeLayout: robocopy seed exit=' + IntToStr(ResultCode))
2616+
else
2617+
AppendInstallLog('EnsureDataRootRuntimeLayout: seeded DataRoot .shim from ProgramRoot');
2618+
end
2619+
else
2620+
AppendInstallLogWarn('EnsureDataRootRuntimeLayout: missing ProgramRoot .shim: ' + ProgramShim);
2621+
2622+
{ Hardlinks to proxy require same volume; certified also keeps proxy on DataRoot. }
2623+
if FileExists(ProgramProxy) then
2624+
begin
2625+
if not FileCopy(ProgramProxy, DataProxy, False) then
2626+
AppendInstallLogWarn('EnsureDataRootRuntimeLayout: failed to copy proxy.exe to DataRoot')
2627+
else
2628+
AppendInstallLog('EnsureDataRootRuntimeLayout: copied proxy.exe to ' + DataProxy);
2629+
end;
2630+
2631+
{ Drop stale ProgramRoot .nodejs so PATH/docs don't point at the empty seed tree. }
2632+
if DirExists(AddBackslash(ProgramRoot) + '.nodejs') then
2633+
begin
2634+
if not Exec(
2635+
ExpandConstant('{cmd}'),
2636+
'/C rmdir "' + AddBackslash(ProgramRoot) + '.nodejs"',
2637+
'',
2638+
SW_HIDE,
2639+
ewWaitUntilTerminated,
2640+
ResultCode
2641+
) then
2642+
AppendInstallLogWarn('EnsureDataRootRuntimeLayout: failed to remove stale ProgramRoot .nodejs')
2643+
else
2644+
AppendInstallLog('EnsureDataRootRuntimeLayout: removed stale ProgramRoot .nodejs junction');
2645+
end;
2646+
end;
2647+
25182648
function EnsureShimModeNodePathJunction(): Boolean;
25192649
var
25202650
NodePath: String;
25212651
ShimPath: String;
2652+
DataRoot: String;
25222653
CommandLine: String;
25232654
ResultCode: Integer;
25242655
begin
@@ -2527,8 +2658,9 @@ begin
25272658
if not IsShimModeSelected() then
25282659
Exit;
25292660
2530-
NodePath := ExpandConstant('{app}\.nodejs');
2531-
ShimPath := ExpandConstant('{app}\.shim');
2661+
DataRoot := NormalizePath(GetDataRoot(''));
2662+
NodePath := AddBackslash(DataRoot) + '.nodejs';
2663+
ShimPath := AddBackslash(DataRoot) + '.shim';
25322664
AppendInstallLog('EnsureShimModeNodePathJunction: source=' + ShimPath + ' target=' + NodePath);
25332665
25342666
if not DirExists(ShimPath) then
@@ -2561,7 +2693,7 @@ begin
25612693
Exit;
25622694
end;
25632695
2564-
AppendInstallLog('EnsureShimModeNodePathJunction: created hidden .nodejs -> .shim');
2696+
AppendInstallLog('EnsureShimModeNodePathJunction: created hidden .nodejs -> .shim under DataRoot');
25652697
end;
25662698
25672699
procedure RegisterInstalledVersionsInWindowsApps();
@@ -2611,10 +2743,18 @@ var
26112743
ResultCode: Integer;
26122744
ShimPath: String;
26132745
ProxyPath: String;
2746+
DataRoot: String;
2747+
DataShim: String;
26142748
CommandLine: String;
26152749
begin
2616-
ShimPath := ExpandConstant('{app}\.shim\' + ShimBaseName + '.exe');
2617-
ProxyPath := ExpandConstant('{app}\utils\proxy.exe');
2750+
DataRoot := NormalizePath(GetDataRoot(''));
2751+
DataShim := AddBackslash(DataRoot) + '.shim';
2752+
ShimPath := AddBackslash(DataShim) + ShimBaseName + '.exe';
2753+
2754+
{ Prefer DataRoot proxy when present (required for cross-volume hardlinks). }
2755+
ProxyPath := AddBackslash(DataRoot) + 'proxy.exe';
2756+
if not FileExists(ProxyPath) then
2757+
ProxyPath := ExpandConstant('{app}\utils\proxy.exe');
26182758
26192759
if FileExists(ShimPath) then
26202760
Exit;
@@ -2625,9 +2765,9 @@ begin
26252765
Exit;
26262766
end;
26272767
2628-
if not DirExists(ExpandConstant('{app}\.shim')) then
2768+
if not DirExists(DataShim) then
26292769
begin
2630-
AppendInstallLogWarn('EnsureShimHardlinkForPrewarm: missing .shim directory');
2770+
AppendInstallLogWarn('EnsureShimHardlinkForPrewarm: missing .shim directory: ' + DataShim);
26312771
Exit;
26322772
end;
26332773
@@ -2650,10 +2790,12 @@ end;
26502790
procedure TryPrewarmShim(const ShimBaseName: String);
26512791
var
26522792
ResultCode: Integer;
2793+
ShimPath: String;
26532794
begin
26542795
ResultCode := -1;
2796+
ShimPath := AddBackslash(GetDataRoot('')) + '.shim\' + ShimBaseName + '.exe';
26552797
if Exec(
2656-
ExpandConstant('{app}\.shim\' + ShimBaseName + '.exe'),
2798+
ShimPath,
26572799
'--version',
26582800
'',
26592801
SW_HIDE,
@@ -2738,6 +2880,7 @@ begin
27382880
AppendInstallLog('CurStepChanged: ssPostInstall start');
27392881
AppendInstallLog('CurStepChanged: app=' + ExpandConstant('{app}'));
27402882
AppendInstallLog('CurStepChanged: install root=' + GetInstallRoot(''));
2883+
AppendInstallLog('CurStepChanged: data root=' + GetDataRoot(''));
27412884
AppendInstallLog('CurStepChanged: LegacyInstallDir=' + LegacyInstallDir);
27422885
AppendInstallLog('CurStepChanged: LegacySettingsRoot=' + LegacySettingsRoot);
27432886
AppendInstallLog('CurStepChanged: LegacySettingsPath=' + LegacySettingsPath);
@@ -2777,6 +2920,7 @@ begin
27772920
27782921
FinalizingStep := FinalizingStep + 1;
27792922
UpdateFinalizingProgress(FinalizingPage, 'Configuring internal Node.js shim junction...', FinalizingStep, FinalizingTotal);
2923+
EnsureDataRootRuntimeLayout();
27802924
if not EnsureShimModeNodePathJunction() then
27812925
MsgBox(
27822926
'NVM for Windows could not create the internal .nodejs junction required for shim mode.' + #13#10 + #13#10 +

0 commit comments

Comments
 (0)