@@ -4,7 +4,7 @@ import * as util from 'util';
44import * as os from 'os' ;
55import * as path from 'path' ;
66
7- import { canAccess , writeFile , renameFile , readFile } from '../../util/fs' ;
7+ import { canAccess , writeFile , renameFile , readFile , getRealPath } from '../../util/fs' ;
88import { reportError } from '../../error-tracking' ;
99import { OVERRIDE_BIN_PATH } from './terminal-env-overrides' ;
1010
@@ -18,14 +18,21 @@ const SHELL = (process.env.SHELL || '').split('/').slice(-1)[0];
1818const appendOrCreateFile = util . promisify ( fs . appendFile ) ;
1919const appendToFirstExisting = async ( paths : string [ ] , forceWrite : boolean , contents : string ) => {
2020 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 ) ;
2428 }
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.
2532 }
2633
2734 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:
2936 return appendOrCreateFile ( paths . slice ( - 1 ) [ 0 ] , contents ) ;
3037 }
3138} ;
@@ -146,8 +153,13 @@ export const editShellStartupScripts = async () => {
146153 // git-bash ignoring the inherited $PATH.
147154
148155 // .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 ) ;
151163
152164 // Bash login shells use some other files by preference, if they exist.
153165 // Note that on OSX, all shells are login - elsewhere they only are at actual login time.
@@ -192,8 +204,11 @@ export const editShellStartupScripts = async () => {
192204const removeConfigSectionsFromFile = async ( path : string ) => {
193205 let fileLines : string [ ] ;
194206
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+
195210 try {
196- fileLines = ( await readFile ( path , 'utf8' ) ) . split ( '\n' ) ;
211+ fileLines = ( await readFile ( targetPath , 'utf8' ) ) . split ( '\n' ) ;
197212 } catch ( e ) {
198213 // Silently skip any files we can't read
199214 return ;
@@ -211,9 +226,9 @@ const removeConfigSectionsFromFile = async (path: string) => {
211226
212227 // Write & rename to ensure this is atomic, and avoid races here
213228 // as much as we reasonably can.
214- const tempFile = path + Date . now ( ) + '.temp' ;
229+ const tempFile = targetPath + Date . now ( ) + '.temp' ;
215230 await writeFile ( tempFile , fileLines . join ( '\n' ) ) ;
216- return renameFile ( tempFile , path ) ;
231+ return renameFile ( tempFile , targetPath ) ;
217232} ;
218233
219234// Cleanup: strip our extra config line from all config files
0 commit comments