@@ -4,7 +4,7 @@ import * as util from 'util';
4
4
import * as os from 'os' ;
5
5
import * as path from 'path' ;
6
6
7
- import { canAccess , writeFile , renameFile , readFile } from '../../util/fs' ;
7
+ import { canAccess , writeFile , renameFile , readFile , getRealPath } from '../../util/fs' ;
8
8
import { reportError } from '../../error-tracking' ;
9
9
import { OVERRIDE_BIN_PATH } from './terminal-env-overrides' ;
10
10
@@ -18,14 +18,21 @@ const SHELL = (process.env.SHELL || '').split('/').slice(-1)[0];
18
18
const appendOrCreateFile = util . promisify ( fs . appendFile ) ;
19
19
const appendToFirstExisting = async ( paths : string [ ] , forceWrite : boolean , contents : string ) => {
20
20
for ( let path of paths ) {
21
- // Small race here, but end result is ok either way
22
- if ( await canAccess ( path ) ) {
23
- return appendOrCreateFile ( path , contents ) ;
21
+ // Follow the path through symlinks (relatively common for terminal config):
22
+ const realPath = await getRealPath ( path ) ;
23
+ if ( ! realPath ) continue ; // File (or linked file) does not exist
24
+
25
+ if ( await canAccess ( realPath ) ) {
26
+ // Found our first valid readable file - append our extra config
27
+ return appendOrCreateFile ( realPath , contents ) ;
24
28
}
29
+
30
+ // ^ Small races here, if the file content/perms change between check and write, but
31
+ // unlikely to be a major concern - we'll just fail to write, it'll work next time.
25
32
}
26
33
27
34
if ( forceWrite ) {
28
- // If force write is set, write the last file anyway
35
+ // If force write is set, write the last file anyway, even though it didn't exist before:
29
36
return appendOrCreateFile ( paths . slice ( - 1 ) [ 0 ] , contents ) ;
30
37
}
31
38
} ;
@@ -146,8 +153,13 @@ export const editShellStartupScripts = async () => {
146
153
// git-bash ignoring the inherited $PATH.
147
154
148
155
// .profile is used by Dash, Bash sometimes, and by Sh:
149
- appendOrCreateFile ( path . join ( os . homedir ( ) , '.profile' ) , SH_SHELL_PATH_CONFIG )
150
- . catch ( reportError ) ;
156
+ appendToFirstExisting (
157
+ [
158
+ path . join ( os . homedir ( ) , '.profile' )
159
+ ] ,
160
+ true , // We always write .profile
161
+ SH_SHELL_PATH_CONFIG
162
+ ) . catch ( reportError ) ;
151
163
152
164
// Bash login shells use some other files by preference, if they exist.
153
165
// Note that on OSX, all shells are login - elsewhere they only are at actual login time.
@@ -192,8 +204,11 @@ export const editShellStartupScripts = async () => {
192
204
const removeConfigSectionsFromFile = async ( path : string ) => {
193
205
let fileLines : string [ ] ;
194
206
207
+ const targetPath = await getRealPath ( path ) ; // Follow symlinks, if present
208
+ if ( ! targetPath ) return ; // File doesn't exist, no need to clean it up
209
+
195
210
try {
196
- fileLines = ( await readFile ( path , 'utf8' ) ) . split ( '\n' ) ;
211
+ fileLines = ( await readFile ( targetPath , 'utf8' ) ) . split ( '\n' ) ;
197
212
} catch ( e ) {
198
213
// Silently skip any files we can't read
199
214
return ;
@@ -211,9 +226,9 @@ const removeConfigSectionsFromFile = async (path: string) => {
211
226
212
227
// Write & rename to ensure this is atomic, and avoid races here
213
228
// as much as we reasonably can.
214
- const tempFile = path + Date . now ( ) + '.temp' ;
229
+ const tempFile = targetPath + Date . now ( ) + '.temp' ;
215
230
await writeFile ( tempFile , fileLines . join ( '\n' ) ) ;
216
- return renameFile ( tempFile , path ) ;
231
+ return renameFile ( tempFile , targetPath ) ;
217
232
} ;
218
233
219
234
// Cleanup: strip our extra config line from all config files
0 commit comments