Commit | Line | Data |
---|---|---|
6ab025ed | 1 | // SPDX-License-Identifier: LGPL-2.1 |
c877bbd8 JO |
2 | /* |
3 | * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> | |
4 | * | |
c877bbd8 JO |
5 | */ |
6 | ||
5dbcfd93 | 7 | #include <ctype.h> |
5827f2fa | 8 | #include <stdio.h> |
c877bbd8 JO |
9 | #include <string.h> |
10 | #include <dlfcn.h> | |
11 | #include <stdlib.h> | |
12 | #include <sys/types.h> | |
13 | #include <sys/stat.h> | |
14 | #include <unistd.h> | |
15 | #include <dirent.h> | |
16 | #include "event-parse.h" | |
17 | #include "event-utils.h" | |
18 | ||
19 | #define LOCAL_PLUGIN_DIR ".traceevent/plugins" | |
20 | ||
5827f2fa SR |
21 | static struct registered_plugin_options { |
22 | struct registered_plugin_options *next; | |
c32d52b4 | 23 | struct tep_plugin_option *options; |
5827f2fa SR |
24 | } *registered_options; |
25 | ||
26 | static struct trace_plugin_options { | |
27 | struct trace_plugin_options *next; | |
28 | char *plugin; | |
29 | char *option; | |
30 | char *value; | |
31 | } *trace_plugin_options; | |
32 | ||
c877bbd8 JO |
33 | struct plugin_list { |
34 | struct plugin_list *next; | |
35 | char *name; | |
36 | void *handle; | |
37 | }; | |
38 | ||
5dbcfd93 SR |
39 | static void lower_case(char *str) |
40 | { | |
41 | if (!str) | |
42 | return; | |
43 | for (; *str; str++) | |
44 | *str = tolower(*str); | |
45 | } | |
46 | ||
c32d52b4 | 47 | static int update_option_value(struct tep_plugin_option *op, const char *val) |
5dbcfd93 SR |
48 | { |
49 | char *op_val; | |
50 | ||
51 | if (!val) { | |
52 | /* toggle, only if option is boolean */ | |
53 | if (op->value) | |
54 | /* Warn? */ | |
55 | return 0; | |
56 | op->set ^= 1; | |
57 | return 0; | |
58 | } | |
59 | ||
60 | /* | |
61 | * If the option has a value then it takes a string | |
62 | * otherwise the option is a boolean. | |
63 | */ | |
64 | if (op->value) { | |
65 | op->value = val; | |
66 | return 0; | |
67 | } | |
68 | ||
69 | /* Option is boolean, must be either "1", "0", "true" or "false" */ | |
70 | ||
71 | op_val = strdup(val); | |
72 | if (!op_val) | |
73 | return -1; | |
74 | lower_case(op_val); | |
75 | ||
76 | if (strcmp(val, "1") == 0 || strcmp(val, "true") == 0) | |
77 | op->set = 1; | |
78 | else if (strcmp(val, "0") == 0 || strcmp(val, "false") == 0) | |
79 | op->set = 0; | |
80 | free(op_val); | |
81 | ||
82 | return 0; | |
83 | } | |
84 | ||
5827f2fa | 85 | /** |
ca2921dd | 86 | * tep_plugin_list_options - get list of plugin options |
5827f2fa SR |
87 | * |
88 | * Returns an array of char strings that list the currently registered | |
89 | * plugin options in the format of <plugin>:<option>. This list can be | |
90 | * used by toggling the option. | |
91 | * | |
92 | * Returns NULL if there's no options registered. On error it returns | |
93 | * INVALID_PLUGIN_LIST_OPTION | |
94 | * | |
ca2921dd | 95 | * Must be freed with tep_plugin_free_options_list(). |
5827f2fa | 96 | */ |
ca2921dd | 97 | char **tep_plugin_list_options(void) |
5827f2fa SR |
98 | { |
99 | struct registered_plugin_options *reg; | |
c32d52b4 | 100 | struct tep_plugin_option *op; |
5827f2fa SR |
101 | char **list = NULL; |
102 | char *name; | |
103 | int count = 0; | |
104 | ||
105 | for (reg = registered_options; reg; reg = reg->next) { | |
106 | for (op = reg->options; op->name; op++) { | |
107 | char *alias = op->plugin_alias ? op->plugin_alias : op->file; | |
108 | char **temp = list; | |
67dfc376 | 109 | int ret; |
5827f2fa | 110 | |
67dfc376 FV |
111 | ret = asprintf(&name, "%s:%s", alias, op->name); |
112 | if (ret < 0) | |
5827f2fa SR |
113 | goto err; |
114 | ||
5827f2fa SR |
115 | list = realloc(list, count + 2); |
116 | if (!list) { | |
117 | list = temp; | |
118 | free(name); | |
119 | goto err; | |
120 | } | |
121 | list[count++] = name; | |
122 | list[count] = NULL; | |
123 | } | |
124 | } | |
125 | return list; | |
126 | ||
127 | err: | |
128 | while (--count >= 0) | |
129 | free(list[count]); | |
130 | free(list); | |
131 | ||
132 | return INVALID_PLUGIN_LIST_OPTION; | |
133 | } | |
134 | ||
ca2921dd | 135 | void tep_plugin_free_options_list(char **list) |
5827f2fa SR |
136 | { |
137 | int i; | |
138 | ||
139 | if (!list) | |
140 | return; | |
141 | ||
142 | if (list == INVALID_PLUGIN_LIST_OPTION) | |
143 | return; | |
144 | ||
145 | for (i = 0; list[i]; i++) | |
146 | free(list[i]); | |
147 | ||
148 | free(list); | |
149 | } | |
150 | ||
151 | static int | |
c32d52b4 | 152 | update_option(const char *file, struct tep_plugin_option *option) |
5827f2fa SR |
153 | { |
154 | struct trace_plugin_options *op; | |
155 | char *plugin; | |
5dbcfd93 | 156 | int ret = 0; |
5827f2fa SR |
157 | |
158 | if (option->plugin_alias) { | |
159 | plugin = strdup(option->plugin_alias); | |
160 | if (!plugin) | |
161 | return -1; | |
162 | } else { | |
163 | char *p; | |
164 | plugin = strdup(file); | |
165 | if (!plugin) | |
166 | return -1; | |
167 | p = strstr(plugin, "."); | |
168 | if (p) | |
169 | *p = '\0'; | |
170 | } | |
171 | ||
172 | /* first look for named options */ | |
173 | for (op = trace_plugin_options; op; op = op->next) { | |
174 | if (!op->plugin) | |
175 | continue; | |
176 | if (strcmp(op->plugin, plugin) != 0) | |
177 | continue; | |
178 | if (strcmp(op->option, option->name) != 0) | |
179 | continue; | |
180 | ||
5dbcfd93 SR |
181 | ret = update_option_value(option, op->value); |
182 | if (ret) | |
183 | goto out; | |
184 | break; | |
5827f2fa SR |
185 | } |
186 | ||
187 | /* first look for unnamed options */ | |
188 | for (op = trace_plugin_options; op; op = op->next) { | |
189 | if (op->plugin) | |
190 | continue; | |
191 | if (strcmp(op->option, option->name) != 0) | |
192 | continue; | |
193 | ||
5dbcfd93 | 194 | ret = update_option_value(option, op->value); |
5827f2fa SR |
195 | break; |
196 | } | |
197 | ||
198 | out: | |
199 | free(plugin); | |
5dbcfd93 | 200 | return ret; |
5827f2fa SR |
201 | } |
202 | ||
203 | /** | |
ca2921dd | 204 | * tep_plugin_add_options - Add a set of options by a plugin |
5827f2fa SR |
205 | * @name: The name of the plugin adding the options |
206 | * @options: The set of options being loaded | |
207 | * | |
208 | * Sets the options with the values that have been added by user. | |
209 | */ | |
ca2921dd TSV |
210 | int tep_plugin_add_options(const char *name, |
211 | struct tep_plugin_option *options) | |
5827f2fa SR |
212 | { |
213 | struct registered_plugin_options *reg; | |
214 | ||
215 | reg = malloc(sizeof(*reg)); | |
216 | if (!reg) | |
217 | return -1; | |
218 | reg->next = registered_options; | |
219 | reg->options = options; | |
220 | registered_options = reg; | |
221 | ||
222 | while (options->name) { | |
223 | update_option(name, options); | |
224 | options++; | |
225 | } | |
226 | return 0; | |
227 | } | |
228 | ||
229 | /** | |
ca2921dd TSV |
230 | * tep_plugin_remove_options - remove plugin options that were registered |
231 | * @options: Options to removed that were registered with tep_plugin_add_options | |
5827f2fa | 232 | */ |
ca2921dd | 233 | void tep_plugin_remove_options(struct tep_plugin_option *options) |
5827f2fa SR |
234 | { |
235 | struct registered_plugin_options **last; | |
236 | struct registered_plugin_options *reg; | |
237 | ||
238 | for (last = ®istered_options; *last; last = &(*last)->next) { | |
239 | if ((*last)->options == options) { | |
240 | reg = *last; | |
241 | *last = reg->next; | |
242 | free(reg); | |
243 | return; | |
244 | } | |
245 | } | |
246 | } | |
247 | ||
248 | /** | |
ca2921dd | 249 | * tep_print_plugins - print out the list of plugins loaded |
5827f2fa SR |
250 | * @s: the trace_seq descripter to write to |
251 | * @prefix: The prefix string to add before listing the option name | |
252 | * @suffix: The suffix string ot append after the option name | |
fc9b6971 | 253 | * @list: The list of plugins (usually returned by tep_load_plugins() |
5827f2fa SR |
254 | * |
255 | * Writes to the trace_seq @s the list of plugins (files) that is | |
fc9b6971 | 256 | * returned by tep_load_plugins(). Use @prefix and @suffix for formating: |
5827f2fa SR |
257 | * @prefix = " ", @suffix = "\n". |
258 | */ | |
ca2921dd TSV |
259 | void tep_print_plugins(struct trace_seq *s, |
260 | const char *prefix, const char *suffix, | |
261 | const struct plugin_list *list) | |
5827f2fa SR |
262 | { |
263 | while (list) { | |
264 | trace_seq_printf(s, "%s%s%s", prefix, list->name, suffix); | |
265 | list = list->next; | |
266 | } | |
267 | } | |
268 | ||
c877bbd8 | 269 | static void |
096177a8 | 270 | load_plugin(struct tep_handle *pevent, const char *path, |
c877bbd8 JO |
271 | const char *file, void *data) |
272 | { | |
273 | struct plugin_list **plugin_list = data; | |
c32d52b4 | 274 | tep_plugin_load_func func; |
c877bbd8 JO |
275 | struct plugin_list *list; |
276 | const char *alias; | |
277 | char *plugin; | |
278 | void *handle; | |
67dfc376 | 279 | int ret; |
c877bbd8 | 280 | |
67dfc376 FV |
281 | ret = asprintf(&plugin, "%s/%s", path, file); |
282 | if (ret < 0) { | |
f9bb36af JO |
283 | warning("could not allocate plugin memory\n"); |
284 | return; | |
285 | } | |
c877bbd8 | 286 | |
c877bbd8 JO |
287 | handle = dlopen(plugin, RTLD_NOW | RTLD_GLOBAL); |
288 | if (!handle) { | |
289 | warning("could not load plugin '%s'\n%s\n", | |
290 | plugin, dlerror()); | |
291 | goto out_free; | |
292 | } | |
293 | ||
c32d52b4 | 294 | alias = dlsym(handle, TEP_PLUGIN_ALIAS_NAME); |
c877bbd8 JO |
295 | if (!alias) |
296 | alias = file; | |
297 | ||
c32d52b4 | 298 | func = dlsym(handle, TEP_PLUGIN_LOADER_NAME); |
c877bbd8 JO |
299 | if (!func) { |
300 | warning("could not find func '%s' in plugin '%s'\n%s\n", | |
c32d52b4 | 301 | TEP_PLUGIN_LOADER_NAME, plugin, dlerror()); |
c877bbd8 JO |
302 | goto out_free; |
303 | } | |
304 | ||
f9bb36af JO |
305 | list = malloc(sizeof(*list)); |
306 | if (!list) { | |
307 | warning("could not allocate plugin memory\n"); | |
308 | goto out_free; | |
309 | } | |
310 | ||
c877bbd8 JO |
311 | list->next = *plugin_list; |
312 | list->handle = handle; | |
313 | list->name = plugin; | |
314 | *plugin_list = list; | |
315 | ||
316 | pr_stat("registering plugin: %s", plugin); | |
317 | func(pevent); | |
318 | return; | |
319 | ||
320 | out_free: | |
321 | free(plugin); | |
322 | } | |
323 | ||
324 | static void | |
096177a8 | 325 | load_plugins_dir(struct tep_handle *pevent, const char *suffix, |
c877bbd8 | 326 | const char *path, |
096177a8 | 327 | void (*load_plugin)(struct tep_handle *pevent, |
c877bbd8 JO |
328 | const char *path, |
329 | const char *name, | |
330 | void *data), | |
331 | void *data) | |
332 | { | |
333 | struct dirent *dent; | |
334 | struct stat st; | |
335 | DIR *dir; | |
336 | int ret; | |
337 | ||
338 | ret = stat(path, &st); | |
339 | if (ret < 0) | |
340 | return; | |
341 | ||
342 | if (!S_ISDIR(st.st_mode)) | |
343 | return; | |
344 | ||
345 | dir = opendir(path); | |
346 | if (!dir) | |
347 | return; | |
348 | ||
349 | while ((dent = readdir(dir))) { | |
350 | const char *name = dent->d_name; | |
351 | ||
352 | if (strcmp(name, ".") == 0 || | |
353 | strcmp(name, "..") == 0) | |
354 | continue; | |
355 | ||
356 | /* Only load plugins that end in suffix */ | |
357 | if (strcmp(name + (strlen(name) - strlen(suffix)), suffix) != 0) | |
358 | continue; | |
359 | ||
360 | load_plugin(pevent, path, name, data); | |
361 | } | |
362 | ||
363 | closedir(dir); | |
364 | } | |
365 | ||
366 | static void | |
096177a8 TSV |
367 | load_plugins(struct tep_handle *pevent, const char *suffix, |
368 | void (*load_plugin)(struct tep_handle *pevent, | |
c877bbd8 JO |
369 | const char *path, |
370 | const char *name, | |
371 | void *data), | |
372 | void *data) | |
373 | { | |
374 | char *home; | |
375 | char *path; | |
376 | char *envdir; | |
67dfc376 | 377 | int ret; |
c877bbd8 | 378 | |
6fed932e | 379 | if (pevent->flags & TEP_DISABLE_PLUGINS) |
a7c3196c SRRH |
380 | return; |
381 | ||
c877bbd8 JO |
382 | /* |
383 | * If a system plugin directory was defined, | |
384 | * check that first. | |
385 | */ | |
386 | #ifdef PLUGIN_DIR | |
6fed932e | 387 | if (!(pevent->flags & TEP_DISABLE_SYS_PLUGINS)) |
a7c3196c SRRH |
388 | load_plugins_dir(pevent, suffix, PLUGIN_DIR, |
389 | load_plugin, data); | |
c877bbd8 JO |
390 | #endif |
391 | ||
392 | /* | |
393 | * Next let the environment-set plugin directory | |
394 | * override the system defaults. | |
395 | */ | |
396 | envdir = getenv("TRACEEVENT_PLUGIN_DIR"); | |
397 | if (envdir) | |
398 | load_plugins_dir(pevent, suffix, envdir, load_plugin, data); | |
399 | ||
400 | /* | |
401 | * Now let the home directory override the environment | |
402 | * or system defaults. | |
403 | */ | |
404 | home = getenv("HOME"); | |
405 | if (!home) | |
406 | return; | |
407 | ||
67dfc376 FV |
408 | ret = asprintf(&path, "%s/%s", home, LOCAL_PLUGIN_DIR); |
409 | if (ret < 0) { | |
f9bb36af JO |
410 | warning("could not allocate plugin memory\n"); |
411 | return; | |
412 | } | |
c877bbd8 | 413 | |
c877bbd8 JO |
414 | load_plugins_dir(pevent, suffix, path, load_plugin, data); |
415 | ||
416 | free(path); | |
417 | } | |
418 | ||
419 | struct plugin_list* | |
fc9b6971 | 420 | tep_load_plugins(struct tep_handle *pevent) |
c877bbd8 JO |
421 | { |
422 | struct plugin_list *list = NULL; | |
423 | ||
424 | load_plugins(pevent, ".so", load_plugin, &list); | |
425 | return list; | |
426 | } | |
427 | ||
428 | void | |
fc9b6971 | 429 | tep_unload_plugins(struct plugin_list *plugin_list, struct tep_handle *pevent) |
c877bbd8 | 430 | { |
c32d52b4 | 431 | tep_plugin_unload_func func; |
c877bbd8 JO |
432 | struct plugin_list *list; |
433 | ||
434 | while (plugin_list) { | |
435 | list = plugin_list; | |
436 | plugin_list = list->next; | |
c32d52b4 | 437 | func = dlsym(list->handle, TEP_PLUGIN_UNLOADER_NAME); |
c877bbd8 | 438 | if (func) |
8d0c2224 | 439 | func(pevent); |
c877bbd8 JO |
440 | dlclose(list->handle); |
441 | free(list->name); | |
442 | free(list); | |
443 | } | |
444 | } |