@@ -61,135 +61,152 @@ static struct {
6161
6262/*!
6363 * \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
6565 *
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
6967 *
70- * \return TRUE if valid name found, FALSE otherwise
68+ * \return \c true if \p name is a valid name, or \c false otherwise
7169 * \note It's reasonable to impose limitations on environment variable names
7270 * beyond what C or setenv() does: We only allow names that contain only
7371 * [a-zA-Z0-9_] characters and do not start with a digit.
7472 */
7573static bool
76- find_env_var_name ( char * line , char * * first , char * * last )
74+ valid_env_var_name ( const gchar * name )
7775{
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;
8279 }
8380
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 ;
88128 }
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 ;
90148 }
91149
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 );
94177}
95178
179+ #define CONTAINER_ENV_FILE "/etc/pacemaker/pcmk-init.env"
180+
96181static void
97- load_env_vars (const char * filename )
182+ load_env_vars (void )
98183{
99184 /* We haven't forked or initialized logging yet, so don't leave any file
100185 * descriptors open, and don't log -- silently ignore errors.
101186 */
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 ;
183190
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 ));
192207 }
208+ fclose (fp );
209+ free (line );
193210}
194211
195212void
@@ -221,7 +238,7 @@ remoted_spawn_pidone(int argc, char **argv)
221238 * To allow for that, look for a special file containing a shell-like syntax
222239 * of name/value pairs, and export those into the environment.
223240 */
224- load_env_vars ("/etc/pacemaker/pcmk-init.env" );
241+ load_env_vars ();
225242
226243 if (strcmp (pid1 , "vars" ) == 0 ) {
227244 return ;
0 commit comments