@@ -61,135 +61,152 @@ static struct {
61
61
62
62
/*!
63
63
* \internal
64
- * \brief Check a line of text for a valid environment variable name
64
+ * \brief Check whether a string is a valid environment variable name
65
65
*
66
- * \param[in] line Text to check
67
- * \param[out] first First character of valid name if found, NULL otherwise
68
- * \param[out] last Last character of valid name if found, NULL otherwise
66
+ * \param[in] name String to check
69
67
*
70
- * \return TRUE if valid name found, FALSE otherwise
68
+ * \return \c true if \p name is a valid name, or \c false otherwise
71
69
* \note It's reasonable to impose limitations on environment variable names
72
70
* beyond what C or setenv() does: We only allow names that contain only
73
71
* [a-zA-Z0-9_] characters and do not start with a digit.
74
72
*/
75
73
static bool
76
- find_env_var_name ( char * line , char * * first , char * * last )
74
+ valid_env_var_name ( const gchar * name )
77
75
{
78
- // Skip leading whitespace
79
- * first = line ;
80
- while (isspace (* * first )) {
81
- ++ * first ;
76
+ if (!isalpha (* name ) && (* name != '_' )) {
77
+ // Invalid first character
78
+ return false;
82
79
}
83
80
84
- if (isalpha (* * first ) || (* * first == '_' )) { // Valid first character
85
- * last = * first ;
86
- while (isalnum (* (* last + 1 )) || (* (* last + 1 ) == '_' )) {
87
- ++ * last ;
81
+ // The rest of the characters must be alphanumeric or underscores
82
+ for (name ++ ; isalnum (* name ) || (* name == '_' ); name ++ );
83
+ return * name == '\0' ;
84
+ }
85
+
86
+ /*!
87
+ * \internal
88
+ * \brief Read one environment variable assignment and set the value
89
+ *
90
+ * Empty lines and trailing comments are ignored. This function handles
91
+ * backslashes, single quotes, and double quotes in a manner similar to a POSIX
92
+ * shell.
93
+ *
94
+ * This function has at least two limitations compared to a shell:
95
+ * * An assignment must be contained within a single line.
96
+ * * Only one assignment per line is supported.
97
+ *
98
+ * It would be possible to get rid of these limitations, but it doesn't seem
99
+ * worth the trouble of implementation and testing.
100
+ *
101
+ * \param[in] line Line containing an environment variable assignment statement
102
+ */
103
+ static void
104
+ load_env_var_line (const char * line )
105
+ {
106
+ gint argc = 0 ;
107
+ gchar * * argv = NULL ;
108
+ GError * error = NULL ;
109
+
110
+ gchar * name = NULL ;
111
+ gchar * value = NULL ;
112
+
113
+ int rc = pcmk_rc_ok ;
114
+ const char * reason = NULL ;
115
+ const char * value_to_set = NULL ;
116
+
117
+ /* g_shell_parse_argv() does the following in a manner similar to the shell:
118
+ * * tokenizes the value
119
+ * * strips a trailing '#' comment if one exists
120
+ * * handles backslashes, single quotes, and double quotes
121
+ */
122
+
123
+ // Ensure the line contains zero or one token besides an optional comment
124
+ if (!g_shell_parse_argv (line , & argc , NULL , & error )) {
125
+ // Empty line (or only space/comment) means nothing to do and no error
126
+ if (error -> code != G_SHELL_ERROR_EMPTY_STRING ) {
127
+ reason = error -> message ;
88
128
}
89
- return TRUE;
129
+ goto done ;
130
+ }
131
+ if (argc != 1 ) {
132
+ // "argc != 1" for sanity; should imply "argc > 1" by now
133
+ reason = "line contains garbage" ;
134
+ goto done ;
135
+ }
136
+
137
+ rc = pcmk__scan_nvpair (line , & name , & value );
138
+ if (rc != pcmk_rc_ok ) {
139
+ reason = pcmk_rc_str (rc );
140
+ goto done ;
141
+ }
142
+
143
+ // Leading whitespace is allowed and ignored. A quoted name is invalid.
144
+ g_strchug (name );
145
+ if (!valid_env_var_name (name )) {
146
+ reason = "invalid environment variable name" ;
147
+ goto done ;
90
148
}
91
149
92
- * first = * last = NULL ;
93
- return FALSE;
150
+ /* Parse the value as the shell would do (stripping outermost quotes, etc.).
151
+ * Also sanity-check that the value either is empty or consists of one
152
+ * token. Anything malformed should have been caught by now.
153
+ */
154
+ if (!g_shell_parse_argv (value , & argc , & argv , & error )) {
155
+ // Parse error should mean value is empty
156
+ CRM_CHECK (error -> code == G_SHELL_ERROR_EMPTY_STRING , goto done );
157
+ value_to_set = "" ;
158
+
159
+ } else {
160
+ // value wasn't empty, so it should contain one token
161
+ CRM_CHECK (argc == 1 , goto done );
162
+ value_to_set = argv [0 ];
163
+ }
164
+
165
+ // Don't overwrite (bundle options take precedence)
166
+ setenv (name , value_to_set , 0 );
167
+
168
+ done :
169
+ if (reason != NULL ) {
170
+ crm_warn ("Failed to perform environment variable assignment '%s': %s" ,
171
+ line , reason );
172
+ }
173
+ g_strfreev (argv );
174
+ g_clear_error (& error );
175
+ g_free (name );
176
+ g_free (value );
94
177
}
95
178
179
+ #define CONTAINER_ENV_FILE "/etc/pacemaker/pcmk-init.env"
180
+
96
181
static void
97
- load_env_vars (const char * filename )
182
+ load_env_vars (void )
98
183
{
99
184
/* We haven't forked or initialized logging yet, so don't leave any file
100
185
* descriptors open, and don't log -- silently ignore errors.
101
186
*/
102
- FILE * fp = fopen (filename , "r" );
103
-
104
- if (fp != NULL ) {
105
- char line [LINE_MAX ] = { '\0' , };
106
-
107
- while (fgets (line , LINE_MAX , fp ) != NULL ) {
108
- char * name = NULL ;
109
- char * end = NULL ;
110
- char * value = NULL ;
111
- char * quote = NULL ;
112
-
113
- // Look for valid name immediately followed by equals sign
114
- if (find_env_var_name (line , & name , & end ) && (* ++ end == '=' )) {
115
-
116
- // Null-terminate name, and advance beyond equals sign
117
- * end ++ = '\0' ;
118
-
119
- // Check whether value is quoted
120
- if ((* end == '\'' ) || (* end == '"' )) {
121
- quote = end ++ ;
122
- }
123
- value = end ;
124
-
125
- if (quote ) {
126
- /* Value is remaining characters up to next non-backslashed
127
- * matching quote character.
128
- */
129
- while (((* end != * quote ) || (* (end - 1 ) == '\\' ))
130
- && (* end != '\0' )) {
131
- end ++ ;
132
- }
133
- if (* end == * quote ) {
134
- // Null-terminate value, and advance beyond close quote
135
- * end ++ = '\0' ;
136
- } else {
137
- // Matching closing quote wasn't found
138
- value = NULL ;
139
- }
140
-
141
- } else {
142
- /* Value is remaining characters up to next non-backslashed
143
- * whitespace.
144
- */
145
- while ((!isspace (* end ) || (* (end - 1 ) == '\\' ))
146
- && (* end != '\0' )) {
147
- ++ end ;
148
- }
149
-
150
- if (end == (line + LINE_MAX - 1 )) {
151
- // Line was too long
152
- value = NULL ;
153
- }
154
- // Do NOT null-terminate value (yet)
155
- }
156
-
157
- /* We have a valid name and value, and end is now the character
158
- * after the closing quote or the first whitespace after the
159
- * unquoted value. Make sure the rest of the line is just
160
- * whitespace or a comment.
161
- */
162
- if (value ) {
163
- char * value_end = end ;
164
-
165
- while (isspace (* end ) && (* end != '\n' )) {
166
- ++ end ;
167
- }
168
- if ((* end == '\n' ) || (* end == '#' )) {
169
- if (quote == NULL ) {
170
- // Now we can null-terminate an unquoted value
171
- * value_end = '\0' ;
172
- }
173
-
174
- // Don't overwrite (bundle options take precedence)
175
- // coverity[tainted_string] This can't easily be changed right now
176
- setenv (name , value , 0 );
177
-
178
- } else {
179
- value = NULL ;
180
- }
181
- }
182
- }
187
+ FILE * fp = fopen (CONTAINER_ENV_FILE , "r" );
188
+ char * line = NULL ;
189
+ size_t buf_size = 0 ;
183
190
184
- if ((value == NULL ) && (strchr (line , '\n' ) == NULL )) {
185
- // Eat remainder of line beyond LINE_MAX
186
- if (fscanf (fp , "%*[^\n]\n" ) == EOF ) {
187
- value = NULL ; // Don't care, make compiler happy
188
- }
189
- }
190
- }
191
- fclose (fp );
191
+ if (fp == NULL ) {
192
+ return ;
193
+ }
194
+
195
+ while (getline (& line , & buf_size , fp ) != -1 ) {
196
+ load_env_var_line (line );
197
+ errno = 0 ;
198
+ }
199
+
200
+ // getline() returns -1 on EOF (expected) or error
201
+ if (errno != 0 ) {
202
+ int rc = errno ;
203
+
204
+ crm_err ("Error while reading environment variables from "
205
+ CONTAINER_ENV_FILE ": %s" ,
206
+ pcmk_rc_str (rc ));
192
207
}
208
+ fclose (fp );
209
+ free (line );
193
210
}
194
211
195
212
void
@@ -221,7 +238,7 @@ remoted_spawn_pidone(int argc, char **argv)
221
238
* To allow for that, look for a special file containing a shell-like syntax
222
239
* of name/value pairs, and export those into the environment.
223
240
*/
224
- load_env_vars ("/etc/pacemaker/pcmk-init.env" );
241
+ load_env_vars ();
225
242
226
243
if (strcmp (pid1 , "vars" ) == 0 ) {
227
244
return ;
0 commit comments