1 /** @file
2 
3   runroot.cc
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22 */
23 
24 /*
25   Please refer to traffic_layout document for the usage
26 */
27 
28 #include "tscore/ink_error.h"
29 #include "tscore/I_Layout.h"
30 #include "tscore/runroot.h"
31 #include <yaml-cpp/yaml.h>
32 
33 static std::string runroot_file = {};
34 
35 // this is a temporary approach and will be replaced when std::filesystem is in
36 bool
exists(const std::string & dir)37 exists(const std::string &dir)
38 {
39   struct stat buffer;
40   int result = stat(dir.c_str(), &buffer);
41   return (!result) ? true : false;
42 }
43 
44 bool
is_directory(const std::string & directory)45 is_directory(const std::string &directory)
46 {
47   struct stat buffer;
48   int result = stat(directory.c_str(), &buffer);
49   return (!result && (S_IFDIR & buffer.st_mode)) ? true : false;
50 }
51 
52 // the function for the checking of the yaml file in the passed in path
53 // if found return the path to the yaml file, if not return empty string.
54 static std::string
get_yaml_path(const std::string & path)55 get_yaml_path(const std::string &path)
56 {
57   // yaml_file 2 and 3 are for temporary use in case the change may break for people using runroot already
58   // this can be removed in the future.
59   std::string yaml_file;
60   std::string yaml_file2;
61   std::string yaml_file3;
62   if (is_directory(path.c_str())) {
63     std::string yaml_file(Layout::relative_to(path, "runroot.yaml"));
64     if (exists(yaml_file)) {
65       return yaml_file;
66     }
67     std::string yaml_file2(Layout::relative_to(path, "runroot.yml"));
68     if (exists(yaml_file2)) {
69       return yaml_file2;
70     }
71     std::string yaml_file3(Layout::relative_to(path, "runroot_path.yml"));
72     if (exists(yaml_file3)) {
73       return yaml_file3;
74     }
75   } else if (exists(path)) {
76     return path;
77   }
78   return {};
79 }
80 
81 // the function for the checking of the yaml file in passed in directory or parent directory
82 // if found return the parent path to the yaml file
83 static std::string
get_parent_yaml_path(const std::string & path)84 get_parent_yaml_path(const std::string &path)
85 {
86   std::string whole_path = path;
87   if (whole_path.back() == '/') {
88     whole_path.pop_back();
89   }
90   // go up to 4 level of parent directories
91   for (int i = 0; i < 4; i++) {
92     if (whole_path.empty()) {
93       return {};
94     }
95     std::string yaml_file = get_yaml_path(whole_path);
96     if (!yaml_file.empty()) {
97       return yaml_file;
98     }
99     whole_path = whole_path.substr(0, whole_path.find_last_of('/'));
100   }
101   return {};
102 }
103 
104 static void
runroot_extra_handling(const char * executable,bool json)105 runroot_extra_handling(const char *executable, bool json)
106 {
107   std::string path;
108   // 2. check Environment variable
109   char *env_val = getenv("TS_RUNROOT");
110   if (env_val) {
111     path = get_yaml_path(env_val);
112     if (!path.empty()) {
113       runroot_file = path;
114       if (!json) {
115         ink_notice("using the environment variable TS_RUNROOT");
116       }
117       return;
118     } else if (!json) {
119       ink_warning("Unable to access runroot: '%s' from $TS_RUNROOT", env_val);
120     }
121   }
122   // 3. find cwd or parent path of cwd to check
123   char cwd[PATH_MAX] = {0};
124   if (getcwd(cwd, sizeof(cwd)) != nullptr) {
125     path = get_parent_yaml_path(cwd);
126     if (!path.empty()) {
127       runroot_file = path;
128       if (!json) {
129         ink_notice("using cwd as TS_RUNROOT");
130       }
131       return;
132     }
133   }
134   // 4. installed executable
135   char RealBinPath[PATH_MAX] = {0};
136   if ((executable != nullptr) && realpath(executable, RealBinPath) != nullptr) {
137     std::string bindir = RealBinPath;
138     bindir             = bindir.substr(0, bindir.find_last_of('/')); // getting the bin dir not executable path
139     path               = get_parent_yaml_path(bindir);
140     if (!path.empty()) {
141       runroot_file = path;
142       if (!json) {
143         ink_notice("using the installed dir as TS_RUNROOT");
144       }
145       return;
146     }
147   }
148   // 5. if no runroot use, using default build
149   return;
150 }
151 
152 // This is a temporary approach to handle runroot with migration to ArgParser.
153 // When all program use ArgParser, we can remove the runroot_handler below and replace it with this one.
154 void
argparser_runroot_handler(std::string const & value,const char * executable,bool json)155 argparser_runroot_handler(std::string const &value, const char *executable, bool json)
156 {
157   // 1.if --run-root is provided
158   if (!value.empty()) {
159     std::string path = get_yaml_path(value);
160     if (!path.empty()) {
161       if (!json) {
162         ink_notice("using command line path as RUNROOT");
163       }
164       runroot_file = path;
165       return;
166     } else if (!json) {
167       ink_warning("Unable to access runroot: '%s'", value.c_str());
168     }
169   }
170   runroot_extra_handling(executable, json);
171 }
172 
173 // handler for ts runroot
174 // this function set up runroot_file
175 void
runroot_handler(const char ** argv,bool json)176 runroot_handler(const char **argv, bool json)
177 {
178   std::string prefix = "--run-root";
179   std::string path;
180 
181   // check if we have --run-root...
182   std::string arg = {};
183 
184   int i = 0;
185   while (argv[i]) {
186     std::string_view command = argv[i];
187     if (command.substr(0, prefix.size()) == prefix) {
188       arg = command.data();
189       break;
190     }
191     i++;
192   }
193 
194   // if --run-root is provided
195   if (!arg.empty() && arg != prefix) {
196     // 1. pass in path
197     prefix += "=";
198     std::string value = arg.substr(prefix.size(), arg.size() - 1);
199     path              = get_yaml_path(value);
200     if (!path.empty()) {
201       if (!json) {
202         ink_notice("using command line path as RUNROOT");
203       }
204       runroot_file = path;
205       return;
206     } else if (!json) {
207       ink_warning("Unable to access runroot: '%s'", value.c_str());
208     }
209   }
210 
211   runroot_extra_handling(argv[0], json);
212 }
213 
214 // return a map of all path with default layout
215 std::unordered_map<std::string, std::string>
runroot_map_default()216 runroot_map_default()
217 {
218   std::unordered_map<std::string, std::string> map;
219 
220   map[LAYOUT_PREFIX]        = Layout::get()->prefix;
221   map[LAYOUT_EXEC_PREFIX]   = Layout::get()->exec_prefix;
222   map[LAYOUT_BINDIR]        = Layout::get()->bindir;
223   map[LAYOUT_SBINDIR]       = Layout::get()->sbindir;
224   map[LAYOUT_SYSCONFDIR]    = Layout::get()->sysconfdir;
225   map[LAYOUT_DATADIR]       = Layout::get()->datadir;
226   map[LAYOUT_INCLUDEDIR]    = Layout::get()->includedir;
227   map[LAYOUT_LIBDIR]        = Layout::get()->libdir;
228   map[LAYOUT_LIBEXECDIR]    = Layout::get()->libexecdir;
229   map[LAYOUT_LOCALSTATEDIR] = Layout::get()->localstatedir;
230   map[LAYOUT_RUNTIMEDIR]    = Layout::get()->runtimedir;
231   map[LAYOUT_LOGDIR]        = Layout::get()->logdir;
232   // mandir is not needed for runroot
233   map[LAYOUT_CACHEDIR] = Layout::get()->cachedir;
234 
235   return map;
236 }
237 
238 // return a map of all path in runroot.yaml
239 RunrootMapType
runroot_map(const std::string & file)240 runroot_map(const std::string &file)
241 {
242   RunrootMapType map;
243   try {
244     YAML::Node yamlfile = YAML::LoadFile(file);
245     std::string prefix  = file.substr(0, file.find_last_of('/'));
246 
247     for (const auto &it : yamlfile) {
248       // key value pairs of dirs
249       std::string value = it.second.as<std::string>();
250       if (value[0] != '/') {
251         value = Layout::relative_to(prefix, value);
252       }
253       map[it.first.as<std::string>()] = value;
254     }
255   } catch (YAML::Exception &e) {
256     ink_warning("Unable to read '%s': %s", file.c_str(), e.what());
257     ink_notice("Continuing with default value");
258     return RunrootMapType{};
259   }
260   return map;
261 }
262 
263 // check for the using of runroot
264 // a map of all path will be returned
265 // if we do not use runroot, a empty map will be returned.
266 RunrootMapType
check_runroot()267 check_runroot()
268 {
269   if (runroot_file.empty()) {
270     return RunrootMapType{};
271   }
272 
273   int len = runroot_file.size();
274   if ((len + 1) > PATH_NAME_MAX) {
275     ink_fatal("runroot path is too big: %d, max %d\n", len, PATH_NAME_MAX - 1);
276   }
277   return runroot_map(runroot_file);
278 }
279 
280 std::string_view
get_runroot()281 get_runroot()
282 {
283   return runroot_file;
284 }
285