1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . IO ;
4+ using System . Text ;
5+
6+ namespace GitCredentialManager . Interop . Posix ;
7+
8+ public class LinuxSettings : Settings
9+ {
10+ private readonly ITrace _trace ;
11+ private readonly IFileSystem _fs ;
12+
13+ private IDictionary < string , string > _extConfigCache ;
14+
15+ /// <summary>
16+ /// Reads settings from Git configuration, environment variables, and defaults from the
17+ /// /etc/git-credential-manager.d app configuration directory.
18+ /// </summary>
19+ public LinuxSettings ( IEnvironment environment , IGit git , ITrace trace , IFileSystem fs )
20+ : base ( environment , git )
21+ {
22+ EnsureArgument . NotNull ( trace , nameof ( trace ) ) ;
23+ EnsureArgument . NotNull ( fs , nameof ( fs ) ) ;
24+
25+ _trace = trace ;
26+ _fs = fs ;
27+
28+ PlatformUtils . EnsurePosix ( ) ;
29+ }
30+
31+ protected override bool TryGetExternalDefault ( string section , string scope , string property , out string value )
32+ {
33+ value = null ;
34+
35+ _extConfigCache ??= ReadExternalConfiguration ( ) ;
36+
37+ string name = string . IsNullOrWhiteSpace ( scope )
38+ ? $ "{ section } .{ property } "
39+ : $ "{ section } .{ scope } .{ property } ";
40+
41+ // Check if the setting exists in the configuration
42+ if ( ! _extConfigCache ? . TryGetValue ( name , out value ) ?? false )
43+ {
44+ // No property exists (or failed to read config)
45+ return false ;
46+ }
47+
48+ _trace . WriteLine ( $ "Default setting found in app configuration directory: { name } ={ value } ") ;
49+ return true ;
50+ }
51+
52+ private Dictionary < string , string > ReadExternalConfiguration ( )
53+ {
54+ try
55+ {
56+ // Check for system-wide config files in /etc/git-credential-manager/config.d and concatenate them together
57+ // in alphabetical order to form a single configuration.
58+ const string configDir = Constants . LinuxAppDefaultsDirectoryPath ;
59+ if ( ! _fs . DirectoryExists ( configDir ) )
60+ {
61+ // No configuration directory exists
62+ return null ;
63+ }
64+
65+ // Get all the files in the configuration directory
66+ IEnumerable < string > files = _fs . EnumerateFiles ( configDir , "*" ) ;
67+
68+ // Read the contents of each file and concatenate them together
69+ var combinedFile = new StringBuilder ( ) ;
70+ foreach ( string file in files )
71+ {
72+ using Stream stream = _fs . OpenFileStream ( file , FileMode . Open , FileAccess . Read , FileShare . Read ) ;
73+ using var reader = new StreamReader ( stream ) ;
74+ string contents = reader . ReadToEnd ( ) ;
75+ combinedFile . Append ( contents ) ;
76+ combinedFile . Append ( '\n ' ) ;
77+ }
78+
79+ // Parse the configuration in to a dictionary
80+ // Each line is a setting in the format: name=value\n
81+ var config = new Dictionary < string , string > ( GitConfigurationKeyComparer . Instance ) ;
82+ string [ ] lines = combinedFile . ToString ( ) . Split ( '\n ' , StringSplitOptions . RemoveEmptyEntries ) ;
83+ foreach ( string line in lines )
84+ {
85+ if ( string . IsNullOrWhiteSpace ( line ) )
86+ {
87+ // Skip empty lines
88+ continue ;
89+ }
90+
91+ if ( line . TrimStart ( ) . StartsWith ( '#' ) )
92+ {
93+ // Skip comments
94+ continue ;
95+ }
96+
97+ string [ ] parts = line . Split ( '=' , count : 2 ) ;
98+ if ( parts . Length != 2 )
99+ {
100+ // Invalid line format
101+ continue ;
102+ }
103+
104+ string k = parts [ 0 ] ;
105+ string v = parts [ 1 ] ;
106+ config [ k ] = v ;
107+ }
108+
109+ return config ;
110+ }
111+ catch ( Exception ex )
112+ {
113+ // Reading defaults is not critical to the operation of the application
114+ // so we can ignore any errors and just log the failure.
115+ _trace . WriteLine ( "Failed to read default setting from app configuration directory." ) ;
116+ _trace . WriteException ( ex ) ;
117+ return null ;
118+ }
119+ }
120+ }
0 commit comments