1 /* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 // This module exports ParseFlagsFromEnvAndDieIfUnknown(), which allows other
17 // modules to parse flags from an environtment variable, or a file named by the
18 // environment variable.
19
20 #include "tensorflow/compiler/xla/parse_flags_from_env.h"
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include <memory>
27 #include <string>
28 #include <vector>
29
30 #include "absl/container/flat_hash_map.h"
31 #include "absl/strings/ascii.h"
32 #include "absl/strings/str_format.h"
33 #include "absl/strings/str_join.h"
34 #include "absl/types/span.h"
35 #include "tensorflow/compiler/xla/types.h"
36 #include "tensorflow/core/platform/logging.h"
37 #include "tensorflow/core/util/command_line_flags.h"
38
39 namespace xla {
40
41 static const char kWS[] = " \t\r\n"; // whitespace
42
43 // The following struct represents an argv[]-style array, parsed
44 // from data gleaned from the environment.
45 //
46 // As usual, an anonymous namespace is advisable to avoid
47 // constructor/destructor collisions with other "private" types
48 // in the same named namespace.
49 namespace {
50
51 // Functor which deletes objects by calling `free`. Necessary to free strdup'ed
52 // strings created by AppendToEnvArgv.
53 struct FreeDeleter {
operator ()xla::__anonf56e81bf0111::FreeDeleter54 void operator()(char* ptr) { free(ptr); }
55 };
56
57 struct EnvArgv {
EnvArgvxla::__anonf56e81bf0111::EnvArgv58 EnvArgv() : initialized(false), argc(0) {}
59 bool initialized; // whether the other fields have been set.
60 int argc; // elements used in argv[]
61 std::vector<char*> argv; // flag arguments parsed from environment string.
62 // saved values from argv[] to avoid leaks
63 std::vector<std::unique_ptr<char, FreeDeleter>> argv_save;
64 };
65 } // anonymous namespace
66
67 // Append the string s0[0, .., s0len-1] concatenated with s1[0, .., s1len-1] as
68 // a newly allocated nul-terminated string to the array *a. If s0==nullptr, a
69 // nullptr is appended without increasing a->argc.
AppendToEnvArgv(const char * s0,size_t s0len,const char * s1,size_t s1len,EnvArgv * a)70 static void AppendToEnvArgv(const char* s0, size_t s0len, const char* s1,
71 size_t s1len, EnvArgv* a) {
72 if (s0 == nullptr) {
73 a->argv.push_back(nullptr);
74 a->argv_save.push_back(nullptr);
75 } else {
76 std::string s = std::string(s0, s0len) + std::string(s1, s1len);
77 char* str = strdup(s.c_str());
78 a->argv.push_back(str);
79 a->argv_save.emplace_back(str);
80 a->argc++;
81 }
82 }
83
84 // Like s.find_first_of(x, pos), but return s.size() when find_first_of() would
85 // return std::string::npos. This avoids if-statements elsewhere.
FindFirstOf(const std::string & s,const char * x,size_t pos)86 static size_t FindFirstOf(const std::string& s, const char* x, size_t pos) {
87 size_t result = s.find_first_of(x, pos);
88 return result == std::string::npos ? s.size() : result;
89 }
90
91 // Like s.find_first_not_of(x, pos), but return s.size() when
92 // find_first_not_of() would return std::string::npos. This avoids
93 // if-statements elsewhere.
FindFirstNotOf(const std::string & s,const char * x,size_t pos)94 static size_t FindFirstNotOf(const std::string& s, const char* x, size_t pos) {
95 size_t result = s.find_first_not_of(x, pos);
96 return result == std::string::npos ? s.size() : result;
97 }
98
99 // Given a string containing flags, parse them into the XLA command line flags.
100 // The parse is best effort, and gives up on the first syntax error.
ParseArgvFromString(const std::string & flag_str,EnvArgv * a)101 static void ParseArgvFromString(const std::string& flag_str, EnvArgv* a) {
102 size_t b = FindFirstNotOf(flag_str, kWS, 0);
103 while (b != flag_str.size() && flag_str[b] == '-') {
104 // b is the index of the start of a flag.
105 // Set e to the index just past the end of the flag.
106 size_t e = b;
107 while (e != flag_str.size() && isascii(flag_str[e]) &&
108 (strchr("-_", flag_str[e]) != nullptr ||
109 absl::ascii_isalnum(flag_str[e]))) {
110 e++;
111 }
112 if (e != flag_str.size() && flag_str[e] == '=' &&
113 e + 1 != flag_str.size() && strchr("'\"", flag_str[e + 1]) != nullptr) {
114 // A flag of the form --flag="something in double or single quotes"
115 int c;
116 e++; // point just past '='
117 size_t eflag = e;
118 char quote = flag_str[e];
119 e++; // point just past quote
120 // Put in value the string with quotes removed.
121 std::string value;
122 for (; e != flag_str.size() && (c = flag_str[e]) != quote; e++) {
123 if (quote == '"' && c == '\\' && e + 1 != flag_str.size()) {
124 // Handle backslash in double quoted strings. They are literal in
125 // single-quoted strings.
126 e++;
127 c = flag_str[e];
128 }
129 value += c;
130 }
131 if (e != flag_str.size()) { // skip final " or '
132 e++;
133 }
134 AppendToEnvArgv(flag_str.data() + b, eflag - b, value.data(),
135 value.size(), a);
136 } else { // A flag without a quoted value.
137 e = FindFirstOf(flag_str, kWS, e);
138 AppendToEnvArgv(flag_str.data() + b, e - b, "", 0, a);
139 }
140 b = FindFirstNotOf(flag_str, kWS, e);
141 }
142 }
143
144 // Call ParseArgvFromString(..., a) on a string derived from the setting of the
145 // environment variable `envvar`, or a file it points to.
SetArgvFromEnv(absl::string_view envvar,EnvArgv * a)146 static void SetArgvFromEnv(absl::string_view envvar, EnvArgv* a) {
147 if (!a->initialized) {
148 static const char kDummyArgv[] = "<argv[0]>";
149 AppendToEnvArgv(kDummyArgv, strlen(kDummyArgv), nullptr, 0,
150 a); // dummy argv[0]
151 const char* env = getenv(std::string(envvar).c_str());
152 if (env == nullptr || env[0] == '\0') {
153 // nothing
154 } else if (env[strspn(env, kWS)] == '-') { // flags in env var value
155 ParseArgvFromString(env, a);
156 } else { // assume it's a file name
157 FILE* fp = fopen(env, "r");
158 if (fp != nullptr) {
159 std::string str;
160 char buf[512];
161 int n;
162 while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
163 str.append(buf, n);
164 }
165 fclose(fp);
166 ParseArgvFromString(str, a);
167 } else {
168 LOG(QFATAL)
169 << "Could not open file \"" << env
170 << "\" to read flags for environment variable \"" << envvar
171 << "\". (We assumed \"" << env
172 << "\" was a file name because it did not start with a \"--\".)";
173 }
174 }
175 AppendToEnvArgv(nullptr, 0, nullptr, 0, a); // add trailing nullptr to *a.
176 a->initialized = true;
177 }
178 }
179
180 // The simulated argv[] parsed from the environment, one for each different
181 // environment variable we've seen.
EnvArgvs()182 static absl::flat_hash_map<std::string, EnvArgv>& EnvArgvs() {
183 static auto* env_argvs = new absl::flat_hash_map<std::string, EnvArgv>();
184 return *env_argvs;
185 }
186
187 // Used to protect accesses to env_argvs.
188 static absl::Mutex env_argv_mu(absl::kConstInit);
189
ParseFlagsFromEnvAndDieIfUnknown(absl::string_view envvar,const std::vector<tensorflow::Flag> & flag_list)190 bool ParseFlagsFromEnvAndDieIfUnknown(
191 absl::string_view envvar, const std::vector<tensorflow::Flag>& flag_list) {
192 absl::MutexLock lock(&env_argv_mu);
193 auto* env_argv = &EnvArgvs()[std::string(envvar)];
194 SetArgvFromEnv(envvar, env_argv); // a no-op if already initialized
195
196 if (VLOG_IS_ON(1)) {
197 VLOG(1) << "For env var " << envvar << " found arguments:";
198 for (int i = 0; i < env_argv->argc; i++) {
199 VLOG(1) << " argv[" << i << "] = " << env_argv->argv[i];
200 }
201 }
202
203 bool result =
204 tensorflow::Flags::Parse(&env_argv->argc, &env_argv->argv[0], flag_list);
205
206 // There's always at least one unparsed argc, namely the fake argv[0].
207 if (result && env_argv->argc != 1) {
208 // Skip the first argv, which is the fake argv[0].
209 auto unknown_flags = absl::MakeSpan(env_argv->argv);
210 unknown_flags.remove_prefix(1);
211
212 // Some flags are set on XLA_FLAGS, others on TF_XLA_FLAGS. If we find an
213 // unrecognized flag, suggest the alternative.
214 std::string alternate_envvar;
215 if (envvar == "TF_XLA_FLAGS") {
216 alternate_envvar = "XLA_FLAGS";
217 } else if (envvar == "XLA_FLAGS") {
218 alternate_envvar = "TF_XLA_FLAGS";
219 }
220 std::string did_you_mean;
221 if (!alternate_envvar.empty()) {
222 did_you_mean = absl::StrFormat(
223 "\nPerhaps you meant to specify these on the %s envvar?",
224 alternate_envvar);
225 }
226
227 LOG(QFATAL) << "Unknown flag" << (unknown_flags.size() > 1 ? "s" : "")
228 << " in " << envvar << ": " << absl::StrJoin(unknown_flags, " ")
229 << did_you_mean;
230 return false;
231 }
232 return result;
233 }
234
235 // Testing only.
236 //
237 // Resets the env_argv struct so that subsequent calls to
238 // ParseFlagsFromEnvAndDieIfUnknown() will parse the environment variable (or
239 // the file it points to) anew, and set *pargc, and *pargv to point to the
240 // internal locations of the argc and argv constructed from the environment.
ResetFlagsFromEnvForTesting(absl::string_view envvar,int ** pargc,std::vector<char * > ** pargv)241 void ResetFlagsFromEnvForTesting(absl::string_view envvar, int** pargc,
242 std::vector<char*>** pargv) {
243 absl::MutexLock lock(&env_argv_mu);
244 EnvArgvs().erase(std::string(envvar));
245 auto& env_argv = EnvArgvs()[std::string(envvar)];
246 *pargc = &env_argv.argc;
247 *pargv = &env_argv.argv;
248 }
249
250 } // namespace xla
251