]>
Commit | Line | Data |
---|---|---|
9abec538 | 1 | // systemtap cache manager |
98f552c2 | 2 | // Copyright (C) 2006-2009 Red Hat Inc. |
9abec538 FCE |
3 | // |
4 | // This file is part of systemtap, and is free software. You can | |
5 | // redistribute it and/or modify it under the terms of the GNU General | |
6 | // Public License (GPL); either version 2, or (at your option) any | |
7 | // later version. | |
8 | ||
98f552c2 | 9 | #include "config.h" |
1b78aef5 DS |
10 | #include "session.h" |
11 | #include "cache.h" | |
12 | #include "util.h" | |
0f5d597d | 13 | #include "stap-probe.h" |
1b78aef5 DS |
14 | #include <cerrno> |
15 | #include <string> | |
d1b3549d | 16 | #include <fstream> |
f74fb737 | 17 | #include <cstring> |
4c797c5e | 18 | #include <cassert> |
8cc3cc33 JS |
19 | #include <sstream> |
20 | #include <vector> | |
1b78aef5 DS |
21 | |
22 | extern "C" { | |
23 | #include <sys/types.h> | |
24 | #include <sys/stat.h> | |
25 | #include <fcntl.h> | |
06c7e057 | 26 | #include <glob.h> |
8cc3cc33 | 27 | #include <regex.h> |
dc7f0a09 CM |
28 | #include <utime.h> |
29 | #include <sys/time.h> | |
47caa991 | 30 | #include <unistd.h> |
1b78aef5 DS |
31 | } |
32 | ||
33 | using namespace std; | |
34 | ||
35 | ||
de0db58a | 36 | #define SYSTEMTAP_CACHE_MAX_FILENAME "cache_mb_limit" |
f0220f40 | 37 | #define SYSTEMTAP_CACHE_DEFAULT_MB 256 |
dc7f0a09 | 38 | #define SYSTEMTAP_CACHE_CLEAN_INTERVAL_FILENAME "cache_clean_interval_s" |
9892f8aa | 39 | #define SYSTEMTAP_CACHE_CLEAN_DEFAULT_INTERVAL_S 300 |
de0db58a JS |
40 | |
41 | struct cache_ent_info { | |
8cc3cc33 JS |
42 | vector<string> paths; |
43 | off_t size; // sum across all paths | |
44 | time_t mtime; // newest of all paths | |
45 | ||
46 | cache_ent_info(const vector<string>& paths); | |
47 | bool operator<(const struct cache_ent_info& other) const; | |
de0db58a JS |
48 | void unlink() const; |
49 | }; | |
50 | ||
de0db58a | 51 | |
1b78aef5 | 52 | void |
9b3c54b2 | 53 | add_stapconf_to_cache(systemtap_session& s) |
1b78aef5 | 54 | { |
e16dc041 JS |
55 | bool verbose = s.verbose > 1; |
56 | ||
de0db58a | 57 | string stapconf_src_path = s.tmpdir + "/" + s.stapconf_name; |
e16dc041 | 58 | if (!copy_file(stapconf_src_path, s.stapconf_path, verbose)) |
de0db58a | 59 | { |
63d530ab JS |
60 | // NB: this is not so severe as to prevent reuse of the .ko |
61 | // already copied. | |
62 | // | |
63 | // s.use_script_cache = false; | |
64 | // return; | |
de0db58a | 65 | } |
9b3c54b2 JS |
66 | } |
67 | ||
68 | ||
69 | void | |
70 | add_script_to_cache(systemtap_session& s) | |
71 | { | |
72 | bool verbose = s.verbose > 1; | |
73 | ||
74 | // PR10543: clean the cache *before* we try putting something new into it. | |
75 | // We don't want to risk having the brand new contents being erased again. | |
76 | clean_cache(s); | |
de0db58a | 77 | |
4441e344 | 78 | string module_src_path = s.tmpdir + "/" + s.module_filename(); |
0f5d597d | 79 | PROBE2(stap, cache__add__module, module_src_path.c_str(), s.hash_path.c_str()); |
e16dc041 | 80 | if (!copy_file(module_src_path, s.hash_path, verbose)) |
1b78aef5 | 81 | { |
63d530ab | 82 | s.use_script_cache = false; |
1b78aef5 DS |
83 | return; |
84 | } | |
64211010 DB |
85 | // Copy the signature file, if any. It is not an error if this fails. |
86 | if (file_exists (module_src_path + ".sgn")) | |
87 | copy_file(module_src_path + ".sgn", s.hash_path + ".sgn", verbose); | |
1b78aef5 DS |
88 | |
89 | string c_dest_path = s.hash_path; | |
4441e344 | 90 | if (endswith(c_dest_path, ".ko") || endswith(c_dest_path, ".so")) |
3a894f7e | 91 | c_dest_path.resize(c_dest_path.size() - 3); |
1b78aef5 DS |
92 | c_dest_path += ".c"; |
93 | ||
0f5d597d | 94 | PROBE2(stap, cache__add__source, s.translated_source.c_str(), c_dest_path.c_str()); |
e16dc041 | 95 | if (!copy_file(s.translated_source, c_dest_path, verbose)) |
1b78aef5 | 96 | { |
f4d5049b FCE |
97 | // NB: this is not so severe as to prevent reuse of the .ko |
98 | // already copied. | |
99 | // | |
63d530ab | 100 | // s.use_script_cache = false; |
1b78aef5 DS |
101 | } |
102 | } | |
103 | ||
104 | ||
105 | bool | |
9b3c54b2 | 106 | get_stapconf_from_cache(systemtap_session& s) |
1b78aef5 | 107 | { |
d105f664 JS |
108 | if (s.poison_cache) |
109 | return false; | |
110 | ||
de0db58a | 111 | string stapconf_dest_path = s.tmpdir + "/" + s.stapconf_name; |
9b3c54b2 | 112 | int fd_stapconf; |
1b78aef5 | 113 | |
de0db58a JS |
114 | // See if stapconf exists |
115 | fd_stapconf = open(s.stapconf_path.c_str(), O_RDONLY); | |
116 | if (fd_stapconf == -1) | |
117 | { | |
118 | // It isn't in cache. | |
119 | return false; | |
120 | } | |
121 | ||
122 | // Copy the stapconf header file to the destination | |
a5751672 JS |
123 | if (!get_file_size(fd_stapconf) || |
124 | !copy_file(s.stapconf_path, stapconf_dest_path)) | |
de0db58a | 125 | { |
de0db58a JS |
126 | close(fd_stapconf); |
127 | return false; | |
128 | } | |
129 | ||
130 | // We're done with this file handle. | |
131 | close(fd_stapconf); | |
132 | ||
133 | if (s.verbose > 1) | |
18d6fad8 | 134 | clog << _("Pass 4: using cached ") << s.stapconf_path << endl; |
9b3c54b2 JS |
135 | |
136 | return true; | |
137 | } | |
138 | ||
139 | ||
140 | bool | |
141 | get_script_from_cache(systemtap_session& s) | |
142 | { | |
d105f664 JS |
143 | if (s.poison_cache) |
144 | return false; | |
145 | ||
4441e344 | 146 | string module_dest_path = s.tmpdir + "/" + s.module_filename(); |
9b3c54b2 JS |
147 | string c_src_path = s.hash_path; |
148 | int fd_module, fd_c; | |
149 | ||
4441e344 | 150 | if (endswith(c_src_path, ".ko") || endswith(c_src_path, ".so")) |
9b3c54b2 JS |
151 | c_src_path.resize(c_src_path.size() - 3); |
152 | c_src_path += ".c"; | |
de0db58a | 153 | |
1b78aef5 DS |
154 | // See if module exists |
155 | fd_module = open(s.hash_path.c_str(), O_RDONLY); | |
156 | if (fd_module == -1) | |
157 | { | |
158 | // It isn't in cache. | |
159 | return false; | |
160 | } | |
161 | ||
162 | // See if C file exists. | |
163 | fd_c = open(c_src_path.c_str(), O_RDONLY); | |
164 | if (fd_c == -1) | |
165 | { | |
166 | // The module is there, but the C file isn't. Cleanup and | |
167 | // return. | |
168 | close(fd_module); | |
169 | unlink(s.hash_path.c_str()); | |
170 | return false; | |
171 | } | |
172 | ||
a5751672 JS |
173 | // Check that the files aren't empty, and then |
174 | // copy the cached C file to the destination | |
175 | if (!get_file_size(fd_module) || !get_file_size(fd_c) || | |
176 | !copy_file(c_src_path, s.translated_source)) | |
1b78aef5 | 177 | { |
1b78aef5 DS |
178 | close(fd_module); |
179 | close(fd_c); | |
180 | return false; | |
181 | } | |
182 | ||
183 | // Copy the cached module to the destination (if needed) | |
184 | if (s.last_pass != 3) | |
185 | { | |
e16dc041 | 186 | if (!copy_file(s.hash_path, module_dest_path)) |
1b78aef5 | 187 | { |
1b78aef5 DS |
188 | unlink(c_src_path.c_str()); |
189 | close(fd_module); | |
190 | close(fd_c); | |
191 | return false; | |
192 | } | |
64211010 DB |
193 | // Copy the module signature file, if any. |
194 | // It is not an error if this fails. | |
195 | if (file_exists (s.hash_path + ".sgn")) | |
196 | copy_file(s.hash_path + ".sgn", module_dest_path + ".sgn"); | |
1b78aef5 DS |
197 | } |
198 | ||
d1b3549d DS |
199 | // We're done with these file handles. |
200 | close(fd_module); | |
201 | close(fd_c); | |
202 | ||
203 | // To preserve semantics (since this will happen if we're not | |
204 | // caching), display the C source if the last pass is 3. | |
205 | if (s.last_pass == 3) | |
206 | { | |
207 | ifstream i (s.translated_source.c_str()); | |
208 | cout << i.rdbuf(); | |
209 | } | |
9abec538 FCE |
210 | // And similarly, display probe module name for -p4. |
211 | if (s.last_pass == 4) | |
212 | cout << s.hash_path << endl; | |
d1b3549d | 213 | |
1b78aef5 DS |
214 | // If everything worked, tell the user. We need to do this here, |
215 | // since if copying the cached C file works, but copying the cached | |
216 | // module fails, we remove the cached C file and let the C file get | |
217 | // regenerated. | |
aabba858 FCE |
218 | // NB: don't use s.verbose here, since we're still in pass-2, |
219 | // i.e., s.verbose = s.perpass_verbose[1]. | |
220 | if (s.perpass_verbose[2]) | |
18d6fad8 | 221 | clog << _("Pass 3: using cached ") << c_src_path << endl; |
aabba858 | 222 | if (s.perpass_verbose[3] && s.last_pass != 3) |
18d6fad8 | 223 | clog << _("Pass 4: using cached ") << s.hash_path << endl; |
1b78aef5 | 224 | |
0f5d597d | 225 | PROBE2(stap, cache__get, c_src_path.c_str(), s.hash_path.c_str()); |
ce91eebd | 226 | |
1b78aef5 DS |
227 | return true; |
228 | } | |
06c7e057 KS |
229 | |
230 | ||
d105f664 | 231 | void |
06c7e057 KS |
232 | clean_cache(systemtap_session& s) |
233 | { | |
234 | if (s.cache_path != "") | |
235 | { | |
236 | /* Get cache size limit from file in the stap cache dir */ | |
237 | string cache_max_filename = s.cache_path + "/"; | |
238 | cache_max_filename += SYSTEMTAP_CACHE_MAX_FILENAME; | |
239 | ifstream cache_max_file(cache_max_filename.c_str(), ios::in); | |
fffd8e13 | 240 | unsigned long cache_mb_max; |
06c7e057 KS |
241 | |
242 | if (cache_max_file.is_open()) | |
243 | { | |
fffd8e13 | 244 | cache_max_file >> cache_mb_max; |
06c7e057 | 245 | cache_max_file.close(); |
06c7e057 KS |
246 | } |
247 | else | |
248 | { | |
f604f54a | 249 | //file doesnt exist, create a default size |
4e06b5e2 JS |
250 | ofstream default_cache_max(cache_max_filename.c_str(), ios::out); |
251 | default_cache_max << SYSTEMTAP_CACHE_DEFAULT_MB << endl; | |
252 | cache_mb_max = SYSTEMTAP_CACHE_DEFAULT_MB; | |
06c7e057 | 253 | |
f604f54a | 254 | if (s.verbose > 1) |
46a1a151 JS |
255 | clog << _F("Cache limit file %s/%s missing, creating default.", |
256 | s.cache_path.c_str(), SYSTEMTAP_CACHE_MAX_FILENAME) << endl; | |
06c7e057 KS |
257 | } |
258 | ||
dc7f0a09 CM |
259 | /* Get cache clean interval from file in the stap cache dir */ |
260 | string cache_clean_interval_filename = s.cache_path + "/"; | |
261 | cache_clean_interval_filename += SYSTEMTAP_CACHE_CLEAN_INTERVAL_FILENAME; | |
262 | ifstream cache_clean_interval_file(cache_clean_interval_filename.c_str(), ios::in); | |
263 | unsigned long cache_clean_interval; | |
264 | ||
265 | if (cache_clean_interval_file.is_open()) | |
266 | { | |
267 | cache_clean_interval_file >> cache_clean_interval; | |
268 | cache_clean_interval_file.close(); | |
269 | } | |
270 | else | |
271 | { | |
272 | //file doesnt exist, create a default interval | |
273 | ofstream default_cache_clean_interval(cache_clean_interval_filename.c_str(), ios::out); | |
274 | default_cache_clean_interval << SYSTEMTAP_CACHE_CLEAN_DEFAULT_INTERVAL_S << endl; | |
275 | cache_clean_interval = SYSTEMTAP_CACHE_CLEAN_DEFAULT_INTERVAL_S; | |
276 | ||
277 | if (s.verbose > 1) | |
278 | clog << _F("Cache clean interval file %s missing, creating default.", | |
279 | cache_clean_interval_filename.c_str())<< endl; | |
280 | } | |
281 | ||
282 | /* Check the cache cleaning interval */ | |
283 | struct stat sb; | |
284 | if(stat(cache_clean_interval_filename.c_str(), &sb) < 0) | |
285 | { | |
286 | const char* e = strerror (errno); | |
287 | cerr << _F("clean_cache stat error: %s", e) << endl; | |
288 | return; | |
289 | } | |
290 | ||
291 | struct timeval current_time; | |
292 | gettimeofday(¤t_time, NULL); | |
36fca3d6 | 293 | if(difftime(current_time.tv_sec, sb.st_mtime) < cache_clean_interval) |
dc7f0a09 CM |
294 | { |
295 | //interval not passed, don't continue | |
296 | if (s.verbose > 1) | |
297 | clog << _F("Cache cleaning skipped, interval not reached %lu s / %lu s.", | |
298 | (current_time.tv_sec-sb.st_mtime), cache_clean_interval) << endl; | |
299 | return; | |
300 | } | |
301 | else | |
302 | { | |
303 | //interval reached, continue | |
304 | if (s.verbose > 1) | |
305 | clog << _F("Cleaning cache, interval reached %lu s > %lu s.", | |
306 | (current_time.tv_sec-sb.st_mtime), cache_clean_interval) << endl; | |
307 | } | |
308 | ||
8cc3cc33 | 309 | // glob for all files that look like hashes |
06c7e057 | 310 | glob_t cache_glob; |
8cc3cc33 JS |
311 | ostringstream glob_pattern; |
312 | glob_pattern << s.cache_path << "/*/*"; | |
313 | for (unsigned int i = 0; i < 32; i++) | |
314 | glob_pattern << "[[:xdigit:]]"; | |
315 | glob_pattern << "*"; | |
316 | int rc = glob(glob_pattern.str().c_str(), 0, NULL, &cache_glob); | |
317 | if (rc) { | |
318 | cerr << _F("clean_cache glob error rc=%d", rc) << endl; | |
319 | return; | |
320 | } | |
321 | ||
322 | regex_t hash_len_re; | |
323 | rc = regcomp (&hash_len_re, "([[:xdigit:]]{32}_[[:digit:]]+)", REG_EXTENDED); | |
324 | if (rc) { | |
325 | cerr << _F("clean_cache regcomp error rc=%d", rc) << endl; | |
326 | globfree(&cache_glob); | |
327 | return; | |
328 | } | |
329 | ||
330 | // group all files with the same HASH_LEN | |
331 | map<string, vector<string> > cache_groups; | |
332 | for (size_t i = 0; i < cache_glob.gl_pathc; i++) | |
06c7e057 | 333 | { |
8cc3cc33 JS |
334 | const char* path = cache_glob.gl_pathv[i]; |
335 | regmatch_t hash_len; | |
336 | rc = regexec(&hash_len_re, path, 1, &hash_len, 0); | |
337 | if (rc || hash_len.rm_so == -1 || hash_len.rm_eo == -1) | |
338 | cache_groups[path].push_back(path); // ungrouped | |
339 | else | |
340 | cache_groups[string(path + hash_len.rm_so, | |
341 | hash_len.rm_eo - hash_len.rm_so)] | |
342 | .push_back(path); | |
de0db58a | 343 | } |
8cc3cc33 | 344 | regfree(&hash_len_re); |
de0db58a | 345 | globfree(&cache_glob); |
06c7e057 | 346 | |
2ca81875 | 347 | |
8cc3cc33 JS |
348 | // create each cache entry and accumulate the sum |
349 | off_t cache_size_b = 0; | |
350 | set<cache_ent_info> cache_contents; | |
351 | for (map<string, vector<string> >::const_iterator it = cache_groups.begin(); | |
352 | it != cache_groups.end(); ++it) | |
de0db58a | 353 | { |
8cc3cc33 JS |
354 | cache_ent_info cur_info(it->second); |
355 | if (cache_contents.insert(cur_info).second) | |
356 | cache_size_b += cur_info.size; | |
06c7e057 KS |
357 | } |
358 | ||
fffd8e13 | 359 | unsigned long r_cache_size = cache_size_b; |
8cc3cc33 | 360 | vector<const cache_ent_info*> removed; |
06c7e057 KS |
361 | |
362 | //unlink .ko and .c until the cache size is under the limit | |
8cc3cc33 JS |
363 | for (set<cache_ent_info>::iterator i = cache_contents.begin(); |
364 | i != cache_contents.end(); ++i) | |
06c7e057 | 365 | { |
8cc3cc33 | 366 | if (r_cache_size < cache_mb_max * 1024 * 1024) //convert cache_mb_max to bytes |
06c7e057 KS |
367 | break; |
368 | ||
fffd8e13 | 369 | //remove this (*i) cache_entry, add to removed list |
8cc3cc33 JS |
370 | for (size_t j = 0; j < i->paths.size(); ++j) |
371 | PROBE1(stap, cache__clean, i->paths[j].c_str()); | |
de0db58a | 372 | i->unlink(); |
fffd8e13 | 373 | r_cache_size -= i->size; |
8cc3cc33 | 374 | removed.push_back(&*i); |
06c7e057 KS |
375 | } |
376 | ||
8cc3cc33 | 377 | if (s.verbose > 1 && !removed.empty()) |
06c7e057 | 378 | { |
8cc3cc33 JS |
379 | clog << _("Cache cleaning successful, removed entries: ") << endl; |
380 | for (size_t i = 0; i < removed.size(); ++i) | |
381 | for (size_t j = 0; j < removed[i]->paths.size(); ++j) | |
382 | clog << " " << removed[i]->paths[j] << endl; | |
06c7e057 | 383 | } |
dc7f0a09 CM |
384 | |
385 | if(utime(cache_clean_interval_filename.c_str(), NULL)<0) | |
386 | { | |
387 | const char* e = strerror (errno); | |
388 | cerr << _F("clean_cache utime error: %s", e) << endl; | |
389 | return; | |
390 | } | |
06c7e057 KS |
391 | } |
392 | else | |
393 | { | |
394 | if (s.verbose > 1) | |
18d6fad8 | 395 | clog << _("Cache cleaning skipped, no cache path.") << endl; |
06c7e057 KS |
396 | } |
397 | } | |
398 | ||
06c7e057 | 399 | |
8cc3cc33 JS |
400 | cache_ent_info::cache_ent_info(const vector<string>& paths): |
401 | paths(paths), size(0), mtime(0) | |
402 | { | |
403 | struct stat file_info; | |
404 | for (size_t i = 0; i < paths.size(); ++i) | |
405 | if (stat(paths[i].c_str(), &file_info) == 0) | |
406 | { | |
407 | size += file_info.st_size; | |
408 | if (file_info.st_mtime > mtime) | |
409 | mtime = file_info.st_mtime; | |
410 | } | |
06c7e057 KS |
411 | } |
412 | ||
413 | ||
8cc3cc33 JS |
414 | // The ordering here determines the order that |
415 | // files will be removed from the cache. | |
416 | bool | |
417 | cache_ent_info::operator<(const struct cache_ent_info& other) const | |
06c7e057 | 418 | { |
8cc3cc33 JS |
419 | if (mtime != other.mtime) |
420 | return mtime < other.mtime; | |
421 | if (size != other.size) | |
422 | return size < other.size; | |
423 | if (paths.size() != other.paths.size()) | |
424 | return paths.size() < other.paths.size(); | |
425 | for (size_t i = 0; i < paths.size(); ++i) | |
426 | if (paths[i] != other.paths[i]) | |
427 | return paths[i] < other.paths[i]; | |
428 | return false; | |
de0db58a | 429 | } |
06c7e057 | 430 | |
de0db58a JS |
431 | |
432 | void | |
433 | cache_ent_info::unlink() const | |
434 | { | |
8cc3cc33 JS |
435 | for (size_t i = 0; i < paths.size(); ++i) |
436 | ::unlink(paths[i].c_str()); | |
06c7e057 | 437 | } |
73267b89 | 438 | |
8cc3cc33 | 439 | |
73267b89 | 440 | /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */ |