@@ -83,76 +83,96 @@ valid_env_var_name(const gchar *name)
8383 return * name == '\0' ;
8484}
8585
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+ */
86103static void
87- load_env_var_line (char * line )
104+ load_env_var_line (const char * line )
88105{
106+ gint argc = 0 ;
107+ gchar * * argv = NULL ;
108+ GError * error = NULL ;
109+
89110 gchar * name = NULL ;
90111 gchar * value = NULL ;
91- gchar * end = NULL ;
92- gchar * comment = NULL ;
93112
94- // Strip leading and trailing whitespace
95- g_strstrip (line );
113+ int rc = pcmk_rc_ok ;
114+ const char * reason = NULL ;
115+ const char * value_to_set = NULL ;
96116
97- if ((pcmk__scan_nvpair (line , & name , & value ) != pcmk_rc_ok )
98- || !valid_env_var_name (name )) {
99- goto done ;
100- }
101-
102- if ((* value == '\'' ) || (* value == '"' )) {
103- const char quote = * value ;
104-
105- // Strip the leading quote
106- * value = ' ' ;
107- g_strchug (value );
108-
109- /* Value is remaining characters up to next non-backslashed matching
110- * quote character.
111- */
112- for (end = value ;
113- (* end != '\0' ) && ((* end != quote ) || (* (end - 1 ) == '\\' ));
114- end ++ );
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+ */
115122
116- if (* end != quote ) {
117- // Matching closing quote wasn't found
118- goto done ;
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 ;
119128 }
120-
121- // Discard closing quote and advance to check for trailing garbage
122- * end ++ = '\0' ;
123-
124- } else {
125- // Value is remaining characters up to next non-backslashed whitespace
126- for (end = value ;
127- (* end != '\0' ) && (!isspace (* end ) || (* (end - 1 ) == '\\' ));
128- end ++ );
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 ;
129135 }
130136
131- /* We have a valid name and value, and end is now the character after the
132- * closing quote or the first whitespace after the unquoted value. Make sure
133- * the rest of the line, if any, is just optional whitespace followed by a
134- * comment.
135- */
137+ rc = pcmk__scan_nvpair ( line , & name , & value );
138+ if ( rc != pcmk_rc_ok ) {
139+ reason = pcmk_rc_str ( rc );
140+ goto done ;
141+ }
136142
137- // Strip trailing comment beginning with '#'
138- comment = strchr (end , '#' );
139- if (comment != NULL ) {
140- * comment = '\0' ;
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 ;
141148 }
142149
143- // Strip any remaining trailing whitespace from value
144- g_strchomp (end );
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 = "" ;
145158
146- if (* end != '\0' ) {
147- // Found garbage after value
148- goto done ;
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 ];
149163 }
150164
151165 // Don't overwrite (bundle options take precedence)
152166 // coverity[tainted_string] Can't easily be changed right now
153- setenv (name , value , 0 );
167+ setenv (name , value_to_set , 0 );
154168
155169done :
170+ if (reason != NULL ) {
171+ crm_warn ("Failed to perform environment variable assignment '%s': %s" ,
172+ line , reason );
173+ }
174+ g_strfreev (argv );
175+ g_clear_error (& error );
156176 g_free (name );
157177 g_free (value );
158178}
0 commit comments