1 /*
2  * builtin-buildid-cache.c
3  *
4  * Builtin buildid-cache command: Manages build-id cache
5  *
6  * Copyright (C) 2010, Red Hat Inc.
7  * Copyright (C) 2010, Arnaldo Carvalho de Melo <acme@redhat.com>
8  */
9 #include <sys/types.h>
10 #include <sys/time.h>
11 #include <time.h>
12 #include <dirent.h>
13 #include <errno.h>
14 #include <unistd.h>
15 #include "builtin.h"
16 #include "perf.h"
17 #include "namespaces.h"
18 #include "util/cache.h"
19 #include "util/debug.h"
20 #include "util/header.h"
21 #include <subcmd/parse-options.h>
22 #include "util/strlist.h"
23 #include "util/build-id.h"
24 #include "util/session.h"
25 #include "util/symbol.h"
26 #include "util/time-utils.h"
27 
28 static int build_id_cache__kcore_buildid(const char *proc_dir, char *sbuildid)
29 {
30 	char root_dir[PATH_MAX];
31 	char *p;
32 
33 	strlcpy(root_dir, proc_dir, sizeof(root_dir));
34 
35 	p = strrchr(root_dir, '/');
36 	if (!p)
37 		return -1;
38 	*p = '\0';
39 	return sysfs__sprintf_build_id(root_dir, sbuildid);
40 }
41 
42 static int build_id_cache__kcore_dir(char *dir, size_t sz)
43 {
44 	return fetch_current_timestamp(dir, sz);
45 }
46 
47 static bool same_kallsyms_reloc(const char *from_dir, char *to_dir)
48 {
49 	char from[PATH_MAX];
50 	char to[PATH_MAX];
51 	const char *name;
52 	u64 addr1 = 0, addr2 = 0;
53 	int i, err = -1;
54 
55 	scnprintf(from, sizeof(from), "%s/kallsyms", from_dir);
56 	scnprintf(to, sizeof(to), "%s/kallsyms", to_dir);
57 
58 	for (i = 0; (name = ref_reloc_sym_names[i]) != NULL; i++) {
59 		err = kallsyms__get_function_start(from, name, &addr1);
60 		if (!err)
61 			break;
62 	}
63 
64 	if (err)
65 		return false;
66 
67 	if (kallsyms__get_function_start(to, name, &addr2))
68 		return false;
69 
70 	return addr1 == addr2;
71 }
72 
73 static int build_id_cache__kcore_existing(const char *from_dir, char *to_dir,
74 					  size_t to_dir_sz)
75 {
76 	char from[PATH_MAX];
77 	char to[PATH_MAX];
78 	char to_subdir[PATH_MAX];
79 	struct dirent *dent;
80 	int ret = -1;
81 	DIR *d;
82 
83 	d = opendir(to_dir);
84 	if (!d)
85 		return -1;
86 
87 	scnprintf(from, sizeof(from), "%s/modules", from_dir);
88 
89 	while (1) {
90 		dent = readdir(d);
91 		if (!dent)
92 			break;
93 		if (dent->d_type != DT_DIR)
94 			continue;
95 		scnprintf(to, sizeof(to), "%s/%s/modules", to_dir,
96 			  dent->d_name);
97 		scnprintf(to_subdir, sizeof(to_subdir), "%s/%s",
98 			  to_dir, dent->d_name);
99 		if (!compare_proc_modules(from, to) &&
100 		    same_kallsyms_reloc(from_dir, to_subdir)) {
101 			strlcpy(to_dir, to_subdir, to_dir_sz);
102 			ret = 0;
103 			break;
104 		}
105 	}
106 
107 	closedir(d);
108 
109 	return ret;
110 }
111 
112 static int build_id_cache__add_kcore(const char *filename, bool force)
113 {
114 	char dir[32], sbuildid[SBUILD_ID_SIZE];
115 	char from_dir[PATH_MAX], to_dir[PATH_MAX];
116 	char *p;
117 
118 	strlcpy(from_dir, filename, sizeof(from_dir));
119 
120 	p = strrchr(from_dir, '/');
121 	if (!p || strcmp(p + 1, "kcore"))
122 		return -1;
123 	*p = '\0';
124 
125 	if (build_id_cache__kcore_buildid(from_dir, sbuildid) < 0)
126 		return -1;
127 
128 	scnprintf(to_dir, sizeof(to_dir), "%s/%s/%s",
129 		  buildid_dir, DSO__NAME_KCORE, sbuildid);
130 
131 	if (!force &&
132 	    !build_id_cache__kcore_existing(from_dir, to_dir, sizeof(to_dir))) {
133 		pr_debug("same kcore found in %s\n", to_dir);
134 		return 0;
135 	}
136 
137 	if (build_id_cache__kcore_dir(dir, sizeof(dir)))
138 		return -1;
139 
140 	scnprintf(to_dir, sizeof(to_dir), "%s/%s/%s/%s",
141 		  buildid_dir, DSO__NAME_KCORE, sbuildid, dir);
142 
143 	if (mkdir_p(to_dir, 0755))
144 		return -1;
145 
146 	if (kcore_copy(from_dir, to_dir)) {
147 		/* Remove YYYYmmddHHMMSShh directory */
148 		if (!rmdir(to_dir)) {
149 			p = strrchr(to_dir, '/');
150 			if (p)
151 				*p = '\0';
152 			/* Try to remove buildid directory */
153 			if (!rmdir(to_dir)) {
154 				p = strrchr(to_dir, '/');
155 				if (p)
156 					*p = '\0';
157 				/* Try to remove [kernel.kcore] directory */
158 				rmdir(to_dir);
159 			}
160 		}
161 		return -1;
162 	}
163 
164 	pr_debug("kcore added to build-id cache directory %s\n", to_dir);
165 
166 	return 0;
167 }
168 
169 static int build_id_cache__add_file(const char *filename, struct nsinfo *nsi)
170 {
171 	char sbuild_id[SBUILD_ID_SIZE];
172 	u8 build_id[BUILD_ID_SIZE];
173 	int err;
174 	struct nscookie nsc;
175 
176 	nsinfo__mountns_enter(nsi, &nsc);
177 	err = filename__read_build_id(filename, &build_id, sizeof(build_id));
178 	nsinfo__mountns_exit(&nsc);
179 	if (err < 0) {
180 		pr_debug("Couldn't read a build-id in %s\n", filename);
181 		return -1;
182 	}
183 
184 	build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
185 	err = build_id_cache__add_s(sbuild_id, filename, nsi,
186 				    false, false);
187 	pr_debug("Adding %s %s: %s\n", sbuild_id, filename,
188 		 err ? "FAIL" : "Ok");
189 	return err;
190 }
191 
192 static int build_id_cache__remove_file(const char *filename, struct nsinfo *nsi)
193 {
194 	u8 build_id[BUILD_ID_SIZE];
195 	char sbuild_id[SBUILD_ID_SIZE];
196 	struct nscookie nsc;
197 
198 	int err;
199 
200 	nsinfo__mountns_enter(nsi, &nsc);
201 	err = filename__read_build_id(filename, &build_id, sizeof(build_id));
202 	nsinfo__mountns_exit(&nsc);
203 	if (err < 0) {
204 		pr_debug("Couldn't read a build-id in %s\n", filename);
205 		return -1;
206 	}
207 
208 	build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
209 	err = build_id_cache__remove_s(sbuild_id);
210 	pr_debug("Removing %s %s: %s\n", sbuild_id, filename,
211 		 err ? "FAIL" : "Ok");
212 
213 	return err;
214 }
215 
216 static int build_id_cache__purge_path(const char *pathname, struct nsinfo *nsi)
217 {
218 	struct strlist *list;
219 	struct str_node *pos;
220 	int err;
221 
222 	err = build_id_cache__list_build_ids(pathname, nsi, &list);
223 	if (err)
224 		goto out;
225 
226 	strlist__for_each_entry(pos, list) {
227 		err = build_id_cache__remove_s(pos->s);
228 		pr_debug("Removing %s %s: %s\n", pos->s, pathname,
229 			 err ? "FAIL" : "Ok");
230 		if (err)
231 			break;
232 	}
233 	strlist__delete(list);
234 
235 out:
236 	pr_debug("Purging %s: %s\n", pathname, err ? "FAIL" : "Ok");
237 
238 	return err;
239 }
240 
241 static bool dso__missing_buildid_cache(struct dso *dso, int parm __maybe_unused)
242 {
243 	char filename[PATH_MAX];
244 	u8 build_id[BUILD_ID_SIZE];
245 
246 	if (dso__build_id_filename(dso, filename, sizeof(filename), false) &&
247 	    filename__read_build_id(filename, build_id,
248 				    sizeof(build_id)) != sizeof(build_id)) {
249 		if (errno == ENOENT)
250 			return false;
251 
252 		pr_warning("Problems with %s file, consider removing it from the cache\n",
253 			   filename);
254 	} else if (memcmp(dso->build_id, build_id, sizeof(dso->build_id))) {
255 		pr_warning("Problems with %s file, consider removing it from the cache\n",
256 			   filename);
257 	}
258 
259 	return true;
260 }
261 
262 static int build_id_cache__fprintf_missing(struct perf_session *session, FILE *fp)
263 {
264 	perf_session__fprintf_dsos_buildid(session, fp, dso__missing_buildid_cache, 0);
265 	return 0;
266 }
267 
268 static int build_id_cache__update_file(const char *filename, struct nsinfo *nsi)
269 {
270 	u8 build_id[BUILD_ID_SIZE];
271 	char sbuild_id[SBUILD_ID_SIZE];
272 	struct nscookie nsc;
273 
274 	int err;
275 
276 	nsinfo__mountns_enter(nsi, &nsc);
277 	err = filename__read_build_id(filename, &build_id, sizeof(build_id));
278 	nsinfo__mountns_exit(&nsc);
279 	if (err < 0) {
280 		pr_debug("Couldn't read a build-id in %s\n", filename);
281 		return -1;
282 	}
283 	err = 0;
284 
285 	build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
286 	if (build_id_cache__cached(sbuild_id))
287 		err = build_id_cache__remove_s(sbuild_id);
288 
289 	if (!err)
290 		err = build_id_cache__add_s(sbuild_id, filename, nsi, false,
291 					    false);
292 
293 	pr_debug("Updating %s %s: %s\n", sbuild_id, filename,
294 		 err ? "FAIL" : "Ok");
295 
296 	return err;
297 }
298 
299 int cmd_buildid_cache(int argc, const char **argv)
300 {
301 	struct strlist *list;
302 	struct str_node *pos;
303 	int ret = 0;
304 	int ns_id = -1;
305 	bool force = false;
306 	char const *add_name_list_str = NULL,
307 		   *remove_name_list_str = NULL,
308 		   *purge_name_list_str = NULL,
309 		   *missing_filename = NULL,
310 		   *update_name_list_str = NULL,
311 		   *kcore_filename = NULL;
312 	char sbuf[STRERR_BUFSIZE];
313 
314 	struct perf_data_file file = {
315 		.mode  = PERF_DATA_MODE_READ,
316 	};
317 	struct perf_session *session = NULL;
318 	struct nsinfo *nsi = NULL;
319 
320 	const struct option buildid_cache_options[] = {
321 	OPT_STRING('a', "add", &add_name_list_str,
322 		   "file list", "file(s) to add"),
323 	OPT_STRING('k', "kcore", &kcore_filename,
324 		   "file", "kcore file to add"),
325 	OPT_STRING('r', "remove", &remove_name_list_str, "file list",
326 		    "file(s) to remove"),
327 	OPT_STRING('p', "purge", &purge_name_list_str, "path list",
328 		    "path(s) to remove (remove old caches too)"),
329 	OPT_STRING('M', "missing", &missing_filename, "file",
330 		   "to find missing build ids in the cache"),
331 	OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
332 	OPT_STRING('u', "update", &update_name_list_str, "file list",
333 		    "file(s) to update"),
334 	OPT_INCR('v', "verbose", &verbose, "be more verbose"),
335 	OPT_INTEGER(0, "target-ns", &ns_id, "target pid for namespace context"),
336 	OPT_END()
337 	};
338 	const char * const buildid_cache_usage[] = {
339 		"perf buildid-cache [<options>]",
340 		NULL
341 	};
342 
343 	argc = parse_options(argc, argv, buildid_cache_options,
344 			     buildid_cache_usage, 0);
345 
346 	if (argc || (!add_name_list_str && !kcore_filename &&
347 		     !remove_name_list_str && !purge_name_list_str &&
348 		     !missing_filename && !update_name_list_str))
349 		usage_with_options(buildid_cache_usage, buildid_cache_options);
350 
351 	if (ns_id > 0)
352 		nsi = nsinfo__new(ns_id);
353 
354 	if (missing_filename) {
355 		file.path = missing_filename;
356 		file.force = force;
357 
358 		session = perf_session__new(&file, false, NULL);
359 		if (session == NULL)
360 			return -1;
361 	}
362 
363 	if (symbol__init(session ? &session->header.env : NULL) < 0)
364 		goto out;
365 
366 	setup_pager();
367 
368 	if (add_name_list_str) {
369 		list = strlist__new(add_name_list_str, NULL);
370 		if (list) {
371 			strlist__for_each_entry(pos, list)
372 				if (build_id_cache__add_file(pos->s, nsi)) {
373 					if (errno == EEXIST) {
374 						pr_debug("%s already in the cache\n",
375 							 pos->s);
376 						continue;
377 					}
378 					pr_warning("Couldn't add %s: %s\n",
379 						   pos->s, str_error_r(errno, sbuf, sizeof(sbuf)));
380 				}
381 
382 			strlist__delete(list);
383 		}
384 	}
385 
386 	if (remove_name_list_str) {
387 		list = strlist__new(remove_name_list_str, NULL);
388 		if (list) {
389 			strlist__for_each_entry(pos, list)
390 				if (build_id_cache__remove_file(pos->s, nsi)) {
391 					if (errno == ENOENT) {
392 						pr_debug("%s wasn't in the cache\n",
393 							 pos->s);
394 						continue;
395 					}
396 					pr_warning("Couldn't remove %s: %s\n",
397 						   pos->s, str_error_r(errno, sbuf, sizeof(sbuf)));
398 				}
399 
400 			strlist__delete(list);
401 		}
402 	}
403 
404 	if (purge_name_list_str) {
405 		list = strlist__new(purge_name_list_str, NULL);
406 		if (list) {
407 			strlist__for_each_entry(pos, list)
408 				if (build_id_cache__purge_path(pos->s, nsi)) {
409 					if (errno == ENOENT) {
410 						pr_debug("%s wasn't in the cache\n",
411 							 pos->s);
412 						continue;
413 					}
414 					pr_warning("Couldn't remove %s: %s\n",
415 						   pos->s, str_error_r(errno, sbuf, sizeof(sbuf)));
416 				}
417 
418 			strlist__delete(list);
419 		}
420 	}
421 
422 	if (missing_filename)
423 		ret = build_id_cache__fprintf_missing(session, stdout);
424 
425 	if (update_name_list_str) {
426 		list = strlist__new(update_name_list_str, NULL);
427 		if (list) {
428 			strlist__for_each_entry(pos, list)
429 				if (build_id_cache__update_file(pos->s, nsi)) {
430 					if (errno == ENOENT) {
431 						pr_debug("%s wasn't in the cache\n",
432 							 pos->s);
433 						continue;
434 					}
435 					pr_warning("Couldn't update %s: %s\n",
436 						   pos->s, str_error_r(errno, sbuf, sizeof(sbuf)));
437 				}
438 
439 			strlist__delete(list);
440 		}
441 	}
442 
443 	if (kcore_filename && build_id_cache__add_kcore(kcore_filename, force))
444 		pr_warning("Couldn't add %s\n", kcore_filename);
445 
446 out:
447 	perf_session__delete(session);
448 	nsinfo__zput(nsi);
449 
450 	return ret;
451 }
452