builtin-help.c 11.8 KB
Newer Older
1 2 3 4 5
/*
 * builtin-help.c
 *
 * Builtin help command
 */
6
#include "perf.h"
7
#include "util/config.h"
8
#include "builtin.h"
9
#include <subcmd/exec-cmd.h>
10
#include "common-cmds.h"
11 12 13
#include <subcmd/parse-options.h>
#include <subcmd/run-command.h>
#include <subcmd/help.h>
14
#include "util/debug.h"
15 16 17 18 19 20 21 22 23 24 25 26 27

static struct man_viewer_list {
	struct man_viewer_list *next;
	char name[FLEX_ARRAY];
} *man_viewer_list;

static struct man_viewer_info_list {
	struct man_viewer_info_list *next;
	const char *info;
	char name[FLEX_ARRAY];
} *man_viewer_info_list;

enum help_format {
28
	HELP_FORMAT_NONE,
29 30 31 32 33 34 35 36 37 38 39 40 41
	HELP_FORMAT_MAN,
	HELP_FORMAT_INFO,
	HELP_FORMAT_WEB,
};

static enum help_format parse_help_format(const char *format)
{
	if (!strcmp(format, "man"))
		return HELP_FORMAT_MAN;
	if (!strcmp(format, "info"))
		return HELP_FORMAT_INFO;
	if (!strcmp(format, "web") || !strcmp(format, "html"))
		return HELP_FORMAT_WEB;
42 43 44

	pr_err("unrecognized help format '%s'", format);
	return HELP_FORMAT_NONE;
45 46 47 48 49 50
}

static const char *get_man_viewer_info(const char *name)
{
	struct man_viewer_info_list *viewer;

51
	for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) {
52 53 54 55 56 57 58 59 60 61 62 63
		if (!strcasecmp(name, viewer->name))
			return viewer->info;
	}
	return NULL;
}

static int check_emacsclient_version(void)
{
	struct strbuf buffer = STRBUF_INIT;
	struct child_process ec_process;
	const char *argv_ec[] = { "emacsclient", "--version", NULL };
	int version;
64
	int ret = -1;
65 66 67 68 69 70 71 72 73 74

	/* emacsclient prints its version number on stderr */
	memset(&ec_process, 0, sizeof(ec_process));
	ec_process.argv = argv_ec;
	ec_process.err = -1;
	ec_process.stdout_to_stderr = 1;
	if (start_command(&ec_process)) {
		fprintf(stderr, "Failed to start emacsclient.\n");
		return -1;
	}
75 76 77 78
	if (strbuf_read(&buffer, ec_process.err, 20) < 0) {
		fprintf(stderr, "Failed to read emacsclient version\n");
		goto out;
	}
79 80 81 82 83 84 85 86 87 88
	close(ec_process.err);

	/*
	 * Don't bother checking return value, because "emacsclient --version"
	 * seems to always exits with code 1.
	 */
	finish_command(&ec_process);

	if (prefixcmp(buffer.buf, "emacsclient")) {
		fprintf(stderr, "Failed to parse emacsclient version.\n");
89
		goto out;
90 91
	}

92
	version = atoi(buffer.buf + strlen("emacsclient"));
93 94 95 96 97

	if (version < 22) {
		fprintf(stderr,
			"emacsclient version '%d' too old (< 22).\n",
			version);
98 99 100
	} else
		ret = 0;
out:
101
	strbuf_release(&buffer);
102
	return ret;
103 104
}

105
static void exec_woman_emacs(const char *path, const char *page)
106
{
107 108
	char sbuf[STRERR_BUFSIZE];

109 110
	if (!check_emacsclient_version()) {
		/* This works only with emacsclient version >= 22. */
111
		char *man_page;
112 113 114

		if (!path)
			path = "emacsclient";
115 116 117 118
		if (asprintf(&man_page, "(woman \"%s\")", page) > 0) {
			execlp(path, "emacsclient", "-e", man_page, NULL);
			free(man_page);
		}
119
		warning("failed to exec '%s': %s", path,
120
			str_error_r(errno, sbuf, sizeof(sbuf)));
121 122 123
	}
}

124
static void exec_man_konqueror(const char *path, const char *page)
125 126
{
	const char *display = getenv("DISPLAY");
127

128
	if (display && *display) {
129
		char *man_page;
130
		const char *filename = "kfmclient";
131
		char sbuf[STRERR_BUFSIZE];
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147

		/* It's simpler to launch konqueror using kfmclient. */
		if (path) {
			const char *file = strrchr(path, '/');
			if (file && !strcmp(file + 1, "konqueror")) {
				char *new = strdup(path);
				char *dest = strrchr(new, '/');

				/* strlen("konqueror") == strlen("kfmclient") */
				strcpy(dest + 1, "kfmclient");
				path = new;
			}
			if (file)
				filename = file;
		} else
			path = "kfmclient";
148 149 150 151
		if (asprintf(&man_page, "man:%s(1)", page) > 0) {
			execlp(path, filename, "newTab", man_page, NULL);
			free(man_page);
		}
152
		warning("failed to exec '%s': %s", path,
153
			str_error_r(errno, sbuf, sizeof(sbuf)));
154 155 156
	}
}

157
static void exec_man_man(const char *path, const char *page)
158
{
159 160
	char sbuf[STRERR_BUFSIZE];

161 162 163
	if (!path)
		path = "man";
	execlp(path, "man", page, NULL);
164
	warning("failed to exec '%s': %s", path,
165
		str_error_r(errno, sbuf, sizeof(sbuf)));
166 167 168 169
}

static void exec_man_cmd(const char *cmd, const char *page)
{
170
	char sbuf[STRERR_BUFSIZE];
171
	char *shell_cmd;
172

173 174 175 176
	if (asprintf(&shell_cmd, "%s %s", cmd, page) > 0) {
		execl("/bin/sh", "sh", "-c", shell_cmd, NULL);
		free(shell_cmd);
	}
177
	warning("failed to exec '%s': %s", cmd,
178
		str_error_r(errno, sbuf, sizeof(sbuf)));
179 180 181 182 183 184 185 186 187
}

static void add_man_viewer(const char *name)
{
	struct man_viewer_list **p = &man_viewer_list;
	size_t len = strlen(name);

	while (*p)
		p = &((*p)->next);
188
	*p = zalloc(sizeof(**p) + len + 1);
189 190 191 192 193 194 195 196 197 198 199 200 201 202
	strncpy((*p)->name, name, len);
}

static int supported_man_viewer(const char *name, size_t len)
{
	return (!strncasecmp("man", name, len) ||
		!strncasecmp("woman", name, len) ||
		!strncasecmp("konqueror", name, len));
}

static void do_add_man_viewer_info(const char *name,
				   size_t len,
				   const char *value)
{
203
	struct man_viewer_info_list *new = zalloc(sizeof(*new) + len + 1);
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263

	strncpy(new->name, name, len);
	new->info = strdup(value);
	new->next = man_viewer_info_list;
	man_viewer_info_list = new;
}

static int add_man_viewer_path(const char *name,
			       size_t len,
			       const char *value)
{
	if (supported_man_viewer(name, len))
		do_add_man_viewer_info(name, len, value);
	else
		warning("'%s': path for unsupported man viewer.\n"
			"Please consider using 'man.<tool>.cmd' instead.",
			name);

	return 0;
}

static int add_man_viewer_cmd(const char *name,
			      size_t len,
			      const char *value)
{
	if (supported_man_viewer(name, len))
		warning("'%s': cmd for supported man viewer.\n"
			"Please consider using 'man.<tool>.path' instead.",
			name);
	else
		do_add_man_viewer_info(name, len, value);

	return 0;
}

static int add_man_viewer_info(const char *var, const char *value)
{
	const char *name = var + 4;
	const char *subkey = strrchr(name, '.');

	if (!subkey)
		return error("Config with no key for man viewer: %s", name);

	if (!strcmp(subkey, ".path")) {
		if (!value)
			return config_error_nonbool(var);
		return add_man_viewer_path(name, subkey - name, value);
	}
	if (!strcmp(subkey, ".cmd")) {
		if (!value)
			return config_error_nonbool(var);
		return add_man_viewer_cmd(name, subkey - name, value);
	}

	warning("'%s': unsupported man viewer sub key.", subkey);
	return 0;
}

static int perf_help_config(const char *var, const char *value, void *cb)
{
264 265
	enum help_format *help_formatp = cb;

266 267 268
	if (!strcmp(var, "help.format")) {
		if (!value)
			return config_error_nonbool(var);
269 270
		*help_formatp = parse_help_format(value);
		if (*help_formatp == HELP_FORMAT_NONE)
271
			return -1;
272 273 274 275 276 277 278 279 280 281 282
		return 0;
	}
	if (!strcmp(var, "man.viewer")) {
		if (!value)
			return config_error_nonbool(var);
		add_man_viewer(value);
		return 0;
	}
	if (!prefixcmp(var, "man."))
		return add_man_viewer_info(var, value);

283
	return 0;
284 285 286 287 288 289
}

static struct cmdnames main_cmds, other_cmds;

void list_common_cmds_help(void)
{
290
	unsigned int i, longest = 0;
291 292 293 294 295 296

	for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
		if (longest < strlen(common_cmds[i].name))
			longest = strlen(common_cmds[i].name);
	}

297
	puts(" The most commonly used perf commands are:");
298
	for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
299
		printf("   %-*s   ", longest, common_cmds[i].name);
300 301 302 303 304 305 306 307 308 309 310 311
		puts(common_cmds[i].help);
	}
}

static int is_perf_command(const char *s)
{
	return is_in_cmdlist(&main_cmds, s) ||
		is_in_cmdlist(&other_cmds, s);
}

static const char *cmd_to_page(const char *perf_cmd)
{
312 313
	char *s;

314 315 316 317
	if (!perf_cmd)
		return "perf";
	else if (!prefixcmp(perf_cmd, "perf"))
		return perf_cmd;
318 319

	return asprintf(&s, "perf-%s", perf_cmd) < 0 ? NULL : s;
320 321 322 323
}

static void setup_man_path(void)
{
324
	char *new_path;
325 326 327 328 329 330
	const char *old_path = getenv("MANPATH");

	/* We should always put ':' after our path. If there is no
	 * old_path, the ':' at the end will let 'man' to try
	 * system-wide paths after ours to find the manual page. If
	 * there is old_path, we need ':' as delimiter. */
331 332 333 334 335 336
	if (asprintf(&new_path, "%s:%s", system_path(PERF_MAN_PATH), old_path ?: "") > 0) {
		setenv("MANPATH", new_path, 1);
		free(new_path);
	} else {
		error("Unable to setup man path");
	}
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
}

static void exec_viewer(const char *name, const char *page)
{
	const char *info = get_man_viewer_info(name);

	if (!strcasecmp(name, "man"))
		exec_man_man(info, page);
	else if (!strcasecmp(name, "woman"))
		exec_woman_emacs(info, page);
	else if (!strcasecmp(name, "konqueror"))
		exec_man_konqueror(info, page);
	else if (info)
		exec_man_cmd(info, page);
	else
		warning("'%s': unknown man viewer.", name);
}

355
static int show_man_page(const char *perf_cmd)
356 357 358 359 360 361 362 363
{
	struct man_viewer_list *viewer;
	const char *page = cmd_to_page(perf_cmd);
	const char *fallback = getenv("PERF_MAN_VIEWER");

	setup_man_path();
	for (viewer = man_viewer_list; viewer; viewer = viewer->next)
		exec_viewer(viewer->name, page); /* will return when unable */
364

365 366 367
	if (fallback)
		exec_viewer(fallback, page);
	exec_viewer("man", page);
368 369 370

	pr_err("no man viewer handled the request");
	return -1;
371 372
}

373
static int show_info_page(const char *perf_cmd)
374 375 376 377
{
	const char *page = cmd_to_page(perf_cmd);
	setenv("INFOPATH", system_path(PERF_INFO_PATH), 1);
	execlp("info", "info", "perfman", page, NULL);
378
	return -1;
379 380
}

381
static int get_html_page_path(char **page_path, const char *page)
382 383 384 385 386 387
{
	struct stat st;
	const char *html_path = system_path(PERF_HTML_PATH);

	/* Check that we have a perf documentation directory. */
	if (stat(mkpath("%s/perf.html", html_path), &st)
388 389 390 391
	    || !S_ISREG(st.st_mode)) {
		pr_err("'%s': not a documentation directory.", html_path);
		return -1;
	}
392

393
	return asprintf(page_path, "%s/%s.html", html_path, page);
394 395 396 397 398 399 400 401
}

/*
 * If open_html is not defined in a platform-specific way (see for
 * example compat/mingw.h), we use the script web--browse to display
 * HTML.
 */
#ifndef open_html
402
static void open_html(const char *path)
403
{
404
	execl_cmd("web--browse", "-c", "help.browser", path, NULL);
405 406 407
}
#endif

408
static int show_html_page(const char *perf_cmd)
409 410
{
	const char *page = cmd_to_page(perf_cmd);
411
	char *page_path; /* it leaks but we exec bellow */
412

413
	if (get_html_page_path(&page_path, page) < 0)
414
		return -1;
415

416
	open_html(page_path);
417 418

	return 0;
419 420
}

421
int cmd_help(int argc, const char **argv, const char *prefix __maybe_unused)
422
{
423
	bool show_all = false;
424
	enum help_format help_format = HELP_FORMAT_MAN;
425 426 427 428 429 430 431 432 433
	struct option builtin_help_options[] = {
	OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
	OPT_SET_UINT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
	OPT_SET_UINT('w', "web", &help_format, "show manual in web browser",
			HELP_FORMAT_WEB),
	OPT_SET_UINT('i', "info", &help_format, "show info page",
			HELP_FORMAT_INFO),
	OPT_END(),
	};
434 435 436
	const char * const builtin_help_subcommands[] = {
		"buildid-cache", "buildid-list", "diff", "evlist", "help", "list",
		"record", "report", "bench", "stat", "timechart", "top", "annotate",
437
		"script", "sched", "kallsyms", "kmem", "lock", "kvm", "test", "inject", "mem", "data",
438 439 440 441 442 443 444 445
#ifdef HAVE_LIBELF_SUPPORT
		"probe",
#endif
#ifdef HAVE_LIBAUDIT_SUPPORT
		"trace",
#endif
	NULL };
	const char *builtin_help_usage[] = {
446 447 448
		"perf help [--all] [--man|--web|--info] [command]",
		NULL
	};
449
	const char *alias;
450
	int rc = 0;
451

452 453
	load_command_list("perf-", &main_cmds, &other_cmds);

454
	perf_config(perf_help_config, &help_format);
455

456 457
	argc = parse_options_subcommand(argc, argv, builtin_help_options,
			builtin_help_subcommands, builtin_help_usage, 0);
458 459

	if (show_all) {
460
		printf("\n Usage: %s\n\n", perf_usage_string);
461
		list_commands("perf commands", &main_cmds, &other_cmds);
462
		printf(" %s\n\n", perf_more_info_string);
463 464 465 466
		return 0;
	}

	if (!argv[0]) {
467
		printf("\n usage: %s\n\n", perf_usage_string);
468
		list_common_cmds_help();
469
		printf("\n %s\n\n", perf_more_info_string);
470 471 472 473 474 475 476 477 478 479 480
		return 0;
	}

	alias = alias_lookup(argv[0]);
	if (alias && !is_perf_command(argv[0])) {
		printf("`perf %s' is aliased to `%s'\n", argv[0], alias);
		return 0;
	}

	switch (help_format) {
	case HELP_FORMAT_MAN:
481
		rc = show_man_page(argv[0]);
482 483
		break;
	case HELP_FORMAT_INFO:
484
		rc = show_info_page(argv[0]);
485 486
		break;
	case HELP_FORMAT_WEB:
487 488 489 490
		rc = show_html_page(argv[0]);
		break;
	case HELP_FORMAT_NONE:
		/* fall-through */
491
	default:
492
		rc = -1;
493 494 495
		break;
	}

496
	return rc;
497
}