| 1 | |
| 2 | * perf.c
|
| 3 | *
|
| 4 | * Performance analysis utility.
|
| 5 | *
|
| 6 | * This is the main hub from which the sub-commands (perf stat,
|
| 7 | * perf top, perf record, perf report, etc.) are started.
|
| 8 |
|
| 9 | #include "builtin.h"
|
| 10 | #include "perf.h"
|
| 11 |
|
| 12 | #include "util/build-id.h"
|
| 13 | #include "util/cache.h"
|
| 14 | #include "util/env.h"
|
| 15 | #include <internal/lib.h>
|
| 16 | #include <subcmd/exec-cmd.h>
|
| 17 | #include "util/config.h"
|
| 18 | #include <subcmd/run-command.h>
|
| 19 | #include "util/parse-events.h"
|
| 20 | #include <subcmd/parse-options.h>
|
| 21 | #include <subcmd/help.h>
|
| 22 | #include "util/debug.h"
|
| 23 | #include "util/event.h"
|
| 24 | #include "util/util.h"
|
| 25 | #include "ui/ui.h"
|
| 26 | #include "perf-sys.h"
|
| 27 | #include <api/fs/fs.h>
|
| 28 | #include <api/fs/tracing_path.h>
|
| 29 | #include <perf/core.h>
|
| 30 | #include <errno.h>
|
| 31 | #include <pthread.h>
|
| 32 | #include <signal.h>
|
| 33 | #include <stdlib.h>
|
| 34 | #include <time.h>
|
| 35 | #include <sys/types.h>
|
| 36 | #include <sys/stat.h>
|
| 37 | #include <unistd.h>
|
| 38 | #include <linux/kernel.h>
|
| 39 | #include <linux/string.h>
|
| 40 | #include <linux/zalloc.h>
|
| 41 |
|
| 42 | static int use_pager = -1;
|
| 43 | static FILE *debug_fp = NULL;
|
| 44 |
|
| 45 | struct cmd_struct {
|
| 46 | const char *cmd;
|
| 47 | int (*fn)(int, const char **);
|
| 48 | int option;
|
| 49 | };
|
| 50 |
|
| 51 | static const struct cmd_struct commands[] = {
|
| 52 | { "archive", NULL, 0 },
|
| 53 | { "buildid-cache", cmd_buildid_cache, 0 },
|
| 54 | { "buildid-list", cmd_buildid_list, 0 },
|
| 55 | { "check", cmd_check, 0 },
|
| 56 | { "config", cmd_config, 0 },
|
| 57 | { "c2c", cmd_c2c, 0 },
|
| 58 | { "diff", cmd_diff, 0 },
|
| 59 | { "evlist", cmd_evlist, 0 },
|
| 60 | { "help", cmd_help, 0 },
|
| 61 | { "iostat", NULL, 0 },
|
| 62 | { "kallsyms", cmd_kallsyms, 0 },
|
| 63 | { "list", cmd_list, 0 },
|
| 64 | { "record", cmd_record, 0 },
|
| 65 | { "report", cmd_report, 0 },
|
| 66 | { "bench", cmd_bench, 0 },
|
| 67 | { "stat", cmd_stat, 0 },
|
| 68 | #ifdef HAVE_LIBTRACEEVENT
|
| 69 | { "timechart", cmd_timechart, 0 },
|
| 70 | #endif
|
| 71 | { "top", cmd_top, 0 },
|
| 72 | { "annotate", cmd_annotate, 0 },
|
| 73 | { "version", cmd_version, 0 },
|
| 74 | { "script", cmd_script, 0 },
|
| 75 | #ifdef HAVE_LIBTRACEEVENT
|
| 76 | { "sched", cmd_sched, 0 },
|
| 77 | #endif
|
| 78 | #ifdef HAVE_LIBELF_SUPPORT
|
| 79 | { "probe", cmd_probe, 0 },
|
| 80 | #endif
|
| 81 | #ifdef HAVE_LIBTRACEEVENT
|
| 82 | { "kmem", cmd_kmem, 0 },
|
| 83 | { "lock", cmd_lock, 0 },
|
| 84 | #endif
|
| 85 | { "kvm", cmd_kvm, 0 },
|
| 86 | { "test", cmd_test, 0 },
|
| 87 | #if defined(HAVE_LIBTRACEEVENT)
|
| 88 | { "trace", cmd_trace, 0 },
|
| 89 | #endif
|
| 90 | { "inject", cmd_inject, 0 },
|
| 91 | { "mem", cmd_mem, 0 },
|
| 92 | { "data", cmd_data, 0 },
|
| 93 | { "ftrace", cmd_ftrace, 0 },
|
| 94 | { "daemon", cmd_daemon, 0 },
|
| 95 | #ifdef HAVE_LIBTRACEEVENT
|
| 96 | { "kwork", cmd_kwork, 0 },
|
| 97 | #endif
|
| 98 | };
|
| 99 |
|
| 100 | struct pager_config {
|
| 101 | const char *cmd;
|
| 102 | int val;
|
| 103 | };
|
| 104 |
|
| 105 | static bool same_cmd_with_prefix(const char *var, struct pager_config *c,
|
| 106 | const char *header)
|
| 107 | {
|
| 108 | return (strstarts(var, header) && !strcmp(var + strlen(header), c->cmd));
|
| 109 | }
|
| 110 |
|
| 111 | static int pager_command_config(const char *var, const char *value, void *data)
|
| 112 | {
|
| 113 | struct pager_config *c = data;
|
| 114 | if (same_cmd_with_prefix(var, c, "pager."))
|
| 115 | c->val = perf_config_bool(var, value);
|
| 116 | return 0;
|
| 117 | }
|
| 118 |
|
| 119 |
|
| 120 | static int check_pager_config(const char *cmd)
|
| 121 | {
|
| 122 | int err;
|
| 123 | struct pager_config c;
|
| 124 | c.cmd = cmd;
|
| 125 | c.val = -1;
|
| 126 | err = perf_config(pager_command_config, &c);
|
| 127 | return err ?: c.val;
|
| 128 | }
|
| 129 |
|
| 130 | static int browser_command_config(const char *var, const char *value, void *data)
|
| 131 | {
|
| 132 | struct pager_config *c = data;
|
| 133 | if (same_cmd_with_prefix(var, c, "tui."))
|
| 134 | c->val = perf_config_bool(var, value);
|
| 135 | if (same_cmd_with_prefix(var, c, "gtk."))
|
| 136 | c->val = perf_config_bool(var, value) ? 2 : 0;
|
| 137 | return 0;
|
| 138 | }
|
| 139 |
|
| 140 | |
| 141 | * returns 0 for "no tui", 1 for "use tui", 2 for "use gtk",
|
| 142 | * and -1 for "not specified"
|
| 143 |
|
| 144 | static int check_browser_config(const char *cmd)
|
| 145 | {
|
| 146 | int err;
|
| 147 | struct pager_config c;
|
| 148 | c.cmd = cmd;
|
| 149 | c.val = -1;
|
| 150 | err = perf_config(browser_command_config, &c);
|
| 151 | return err ?: c.val;
|
| 152 | }
|
| 153 |
|
| 154 | static void commit_pager_choice(void)
|
| 155 | {
|
| 156 | switch (use_pager) {
|
| 157 | case 0:
|
| 158 | setenv(PERF_PAGER_ENVIRONMENT, "cat", 1);
|
| 159 | break;
|
| 160 | case 1:
|
| 161 |
|
| 162 | break;
|
| 163 | default:
|
| 164 | break;
|
| 165 | }
|
| 166 | }
|
| 167 |
|
| 168 | static int set_debug_file(const char *path)
|
| 169 | {
|
| 170 | debug_fp = fopen(path, "w");
|
| 171 | if (!debug_fp) {
|
| 172 | fprintf(stderr, "Open debug file '%s' failed: %m\n",
|
| 173 | path);
|
| 174 | return -1;
|
| 175 | }
|
| 176 |
|
| 177 | debug_set_file(debug_fp);
|
| 178 | return 0;
|
| 179 | }
|
| 180 |
|
| 181 | static const struct option options[] = {
|
| 182 | OPT_ARGUMENT("help", "help"),
|
| 183 | OPT_ARGUMENT("version", "version"),
|
| 184 | OPT_ARGUMENT("exec-path", "exec-path"),
|
| 185 | OPT_ARGUMENT("html-path", "html-path"),
|
| 186 | OPT_ARGUMENT("paginate", "paginate"),
|
| 187 | OPT_ARGUMENT("no-pager", "no-pager"),
|
| 188 | OPT_ARGUMENT("debugfs-dir", "debugfs-dir"),
|
| 189 | OPT_ARGUMENT("buildid-dir", "buildid-dir"),
|
| 190 | OPT_ARGUMENT("list-cmds", "list-cmds"),
|
| 191 | OPT_ARGUMENT("list-opts", "list-opts"),
|
| 192 | OPT_ARGUMENT("debug", "debug"),
|
| 193 | OPT_ARGUMENT("debug-file", "debug-file"),
|
| 194 | OPT_END()
|
| 195 | };
|
| 196 |
|
| 197 | static int handle_options(const char ***argv, int *argc, int *envchanged)
|
| 198 | {
|
| 199 | int handled = 0;
|
| 200 |
|
| 201 | while (*argc > 0) {
|
| 202 | const char *cmd = (*argv)[0];
|
| 203 | if (cmd[0] != '-')
|
| 204 | break;
|
| 205 |
|
| 206 | |
| 207 | * For legacy reasons, the "version" and "help"
|
| 208 | * commands can be written with "--" prepended
|
| 209 | * to make them look like flags.
|
| 210 |
|
| 211 | if (!strcmp(cmd, "--help") || !strcmp(cmd, "--version"))
|
| 212 | break;
|
| 213 |
|
| 214 | |
| 215 | * Shortcut for '-h' and '-v' options to invoke help
|
| 216 | * and version command.
|
| 217 |
|
| 218 | if (!strcmp(cmd, "-h")) {
|
| 219 | (*argv)[0] = "--help";
|
| 220 | break;
|
| 221 | }
|
| 222 |
|
| 223 | if (!strcmp(cmd, "-v")) {
|
| 224 | (*argv)[0] = "--version";
|
| 225 | break;
|
| 226 | }
|
| 227 |
|
| 228 | if (!strcmp(cmd, "-vv")) {
|
| 229 | (*argv)[0] = "version";
|
| 230 | verbose = 1;
|
| 231 | break;
|
| 232 | }
|
| 233 |
|
| 234 | |
| 235 | * Check remaining flags.
|
| 236 |
|
| 237 | if (strstarts(cmd, CMD_EXEC_PATH)) {
|
| 238 | cmd += strlen(CMD_EXEC_PATH);
|
| 239 | if (*cmd == '=')
|
| 240 | set_argv_exec_path(cmd + 1);
|
| 241 | else {
|
| 242 | puts(get_argv_exec_path());
|
| 243 | exit(0);
|
| 244 | }
|
| 245 | } else if (!strcmp(cmd, "--html-path")) {
|
| 246 | puts(system_path(PERF_HTML_PATH));
|
| 247 | exit(0);
|
| 248 | } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
|
| 249 | use_pager = 1;
|
| 250 | } else if (!strcmp(cmd, "--no-pager")) {
|
| 251 | use_pager = 0;
|
| 252 | if (envchanged)
|
| 253 | *envchanged = 1;
|
| 254 | } else if (!strcmp(cmd, "--debugfs-dir")) {
|
| 255 | if (*argc < 2) {
|
| 256 | fprintf(stderr, "No directory given for --debugfs-dir.\n");
|
| 257 | usage(perf_usage_string);
|
| 258 | }
|
| 259 | tracing_path_set((*argv)[1]);
|
| 260 | if (envchanged)
|
| 261 | *envchanged = 1;
|
| 262 | (*argv)++;
|
| 263 | (*argc)--;
|
| 264 | } else if (!strcmp(cmd, "--buildid-dir")) {
|
| 265 | if (*argc < 2) {
|
| 266 | fprintf(stderr, "No directory given for --buildid-dir.\n");
|
| 267 | usage(perf_usage_string);
|
| 268 | }
|
| 269 | set_buildid_dir((*argv)[1]);
|
| 270 | if (envchanged)
|
| 271 | *envchanged = 1;
|
| 272 | (*argv)++;
|
| 273 | (*argc)--;
|
| 274 | } else if (strstarts(cmd, CMD_DEBUGFS_DIR)) {
|
| 275 | tracing_path_set(cmd + strlen(CMD_DEBUGFS_DIR));
|
| 276 | fprintf(stderr, "dir: %s\n", tracing_path_mount());
|
| 277 | if (envchanged)
|
| 278 | *envchanged = 1;
|
| 279 | } else if (!strcmp(cmd, "--list-cmds")) {
|
| 280 | unsigned int i;
|
| 281 |
|
| 282 | for (i = 0; i < ARRAY_SIZE(commands); i++) {
|
| 283 | const struct cmd_struct *p = commands + i;
|
| 284 | printf("%s ", p->cmd);
|
| 285 | }
|
| 286 | putchar('\n');
|
| 287 | exit(0);
|
| 288 | } else if (!strcmp(cmd, "--list-opts")) {
|
| 289 | unsigned int i;
|
| 290 |
|
| 291 | for (i = 0; i < ARRAY_SIZE(options)-1; i++) {
|
| 292 | const struct option *p = options + i;
|
| 293 | printf("--%s ", p->long_name);
|
| 294 | }
|
| 295 | putchar('\n');
|
| 296 | exit(0);
|
| 297 | } else if (!strcmp(cmd, "--debug")) {
|
| 298 | if (*argc < 2) {
|
| 299 | fprintf(stderr, "No variable specified for --debug.\n");
|
| 300 | usage(perf_usage_string);
|
| 301 | }
|
| 302 | if (perf_debug_option((*argv)[1]))
|
| 303 | usage(perf_usage_string);
|
| 304 |
|
| 305 | (*argv)++;
|
| 306 | (*argc)--;
|
| 307 | } else if (!strcmp(cmd, "--debug-file")) {
|
| 308 | if (*argc < 2) {
|
| 309 | fprintf(stderr, "No path given for --debug-file.\n");
|
| 310 | usage(perf_usage_string);
|
| 311 | }
|
| 312 |
|
| 313 | if (set_debug_file((*argv)[1]))
|
| 314 | usage(perf_usage_string);
|
| 315 |
|
| 316 | (*argv)++;
|
| 317 | (*argc)--;
|
| 318 |
|
| 319 | } else {
|
| 320 | fprintf(stderr, "Unknown option: %s\n", cmd);
|
| 321 | usage(perf_usage_string);
|
| 322 | }
|
| 323 |
|
| 324 | (*argv)++;
|
| 325 | (*argc)--;
|
| 326 | handled++;
|
| 327 | }
|
| 328 | return handled;
|
| 329 | }
|
| 330 |
|
| 331 | #define RUN_SETUP (1<<0)
|
| 332 | #define USE_PAGER (1<<1)
|
| 333 |
|
| 334 | static int run_builtin(const struct cmd_struct *p, int argc, const char **argv)
|
| 335 | {
|
| 336 | int status;
|
| 337 | struct stat st;
|
| 338 |
|
| 339 | if (use_browser == -1)
|
| 340 | use_browser = check_browser_config(p->cmd);
|
| 341 |
|
| 342 | if (use_pager == -1 && p->option & RUN_SETUP)
|
| 343 | use_pager = check_pager_config(p->cmd);
|
| 344 | if (use_pager == -1 && p->option & USE_PAGER)
|
| 345 | use_pager = 1;
|
| 346 | commit_pager_choice();
|
| 347 |
|
| 348 | status = p->fn(argc, argv);
|
| 349 | perf_config__exit();
|
| 350 | exit_browser(status);
|
| 351 |
|
| 352 | if (status)
|
| 353 | return status & 0xff;
|
| 354 |
|
| 355 |
|
| 356 | if (fstat(fileno(stdout), &st))
|
| 357 | return 0;
|
| 358 |
|
| 359 | if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode))
|
| 360 | return 0;
|
| 361 |
|
| 362 | status = 1;
|
| 363 |
|
| 364 | if (fflush(stdout)) {
|
| 365 | fprintf(stderr, "write failure on standard output: %m\n");
|
| 366 | goto out;
|
| 367 | }
|
| 368 | if (ferror(stdout)) {
|
| 369 | fprintf(stderr, "unknown write failure on standard output\n");
|
| 370 | goto out;
|
| 371 | }
|
| 372 | if (fclose(stdout)) {
|
| 373 | fprintf(stderr, "close failed on standard output: %m\n");
|
| 374 | goto out;
|
| 375 | }
|
| 376 | status = 0;
|
| 377 | out:
|
| 378 | return status;
|
| 379 | }
|
| 380 |
|
| 381 | static void handle_internal_command(int argc, const char **argv)
|
| 382 | {
|
| 383 | const char *cmd = argv[0];
|
| 384 | unsigned int i;
|
| 385 |
|
| 386 |
|
| 387 | if (argc > 1 && !strcmp(argv[1], "--help")) {
|
| 388 | argv[1] = argv[0];
|
| 389 | argv[0] = cmd = "help";
|
| 390 | }
|
| 391 |
|
| 392 | for (i = 0; i < ARRAY_SIZE(commands); i++) {
|
| 393 | const struct cmd_struct *p = commands+i;
|
| 394 | if (p->fn == NULL)
|
| 395 | continue;
|
| 396 | if (strcmp(p->cmd, cmd))
|
| 397 | continue;
|
| 398 | exit(run_builtin(p, argc, argv));
|
| 399 | }
|
| 400 | }
|
| 401 |
|
| 402 | static void execv_dashed_external(const char **argv)
|
| 403 | {
|
| 404 | char *cmd;
|
| 405 | const char *tmp;
|
| 406 | int status;
|
| 407 |
|
| 408 | if (asprintf(&cmd, "perf-%s", argv[0]) < 0)
|
| 409 | goto do_die;
|
| 410 |
|
| 411 | |
| 412 | * argv[0] must be the perf command, but the argv array
|
| 413 | * belongs to the caller, and may be reused in
|
| 414 | * subsequent loop iterations. Save argv[0] and
|
| 415 | * restore it on error.
|
| 416 |
|
| 417 | tmp = argv[0];
|
| 418 | argv[0] = cmd;
|
| 419 |
|
| 420 | |
| 421 | * if we fail because the command is not found, it is
|
| 422 | * OK to return. Otherwise, we just pass along the status code.
|
| 423 |
|
| 424 | status = run_command_v_opt(argv, 0);
|
| 425 | if (status != -ERR_RUN_COMMAND_EXEC) {
|
| 426 | if (IS_RUN_COMMAND_ERR(status)) {
|
| 427 | do_die:
|
| 428 | pr_err("FATAL: unable to run '%s'", argv[0]);
|
| 429 | status = -128;
|
| 430 | }
|
| 431 | exit(-status);
|
| 432 | }
|
| 433 | errno = ENOENT;
|
| 434 |
|
| 435 | argv[0] = tmp;
|
| 436 | zfree(&cmd);
|
| 437 | }
|
| 438 |
|
| 439 | static int run_argv(int *argcp, const char ***argv)
|
| 440 | {
|
| 441 |
|
| 442 | handle_internal_command(*argcp, *argv);
|
| 443 |
|
| 444 |
|
| 445 | execv_dashed_external(*argv);
|
| 446 | return 0;
|
| 447 | }
|
| 448 |
|
| 449 | static int libperf_print(enum libperf_print_level level,
|
| 450 | const char *fmt, va_list ap)
|
| 451 | {
|
| 452 | return veprintf(level, verbose, fmt, ap);
|
| 453 | }
|
| 454 |
|
| 455 | int main(int argc, const char **argv)
|
| 456 | {
|
| 457 | int err, done_help = 0;
|
| 458 | const char *cmd;
|
| 459 |
|
| 460 | perf_debug_setup();
|
| 461 |
|
| 462 |
|
| 463 | exec_cmd_init("perf", PREFIX, PERF_EXEC_PATH, EXEC_PATH_ENVIRONMENT);
|
| 464 | pager_init(PERF_PAGER_ENVIRONMENT);
|
| 465 |
|
| 466 | libperf_init(libperf_print);
|
| 467 |
|
| 468 | cmd = extract_argv0_path(argv[0]);
|
| 469 | if (!cmd)
|
| 470 | cmd = "perf-help";
|
| 471 |
|
| 472 | srandom(time(NULL));
|
| 473 |
|
| 474 |
|
| 475 | config_exclusive_filename = getenv("PERF_CONFIG");
|
| 476 |
|
| 477 | err = perf_config(perf_default_config, NULL);
|
| 478 | if (err)
|
| 479 | return err;
|
| 480 | set_buildid_dir(NULL);
|
| 481 |
|
| 482 | |
| 483 | * "perf-xxxx" is the same as "perf xxxx", but we obviously:
|
| 484 | *
|
| 485 | * - cannot take flags in between the "perf" and the "xxxx".
|
| 486 | * - cannot execute it externally (since it would just do
|
| 487 | * the same thing over again)
|
| 488 | *
|
| 489 | * So we just directly call the internal command handler. If that one
|
| 490 | * fails to handle this, then maybe we just run a renamed perf binary
|
| 491 | * that contains a dash in its name. To handle this scenario, we just
|
| 492 | * fall through and ignore the "xxxx" part of the command string.
|
| 493 |
|
| 494 | if (strstarts(cmd, "perf-")) {
|
| 495 | cmd += 5;
|
| 496 | argv[0] = cmd;
|
| 497 | handle_internal_command(argc, argv);
|
| 498 | |
| 499 | * If the command is handled, the above function does not
|
| 500 | * return undo changes and fall through in such a case.
|
| 501 |
|
| 502 | cmd -= 5;
|
| 503 | argv[0] = cmd;
|
| 504 | }
|
| 505 | if (strstarts(cmd, "trace")) {
|
| 506 | #ifndef HAVE_LIBTRACEEVENT
|
| 507 | fprintf(stderr,
|
| 508 | "trace command not available: missing libtraceevent devel package at build time.\n");
|
| 509 | goto out;
|
| 510 | #else
|
| 511 | setup_path();
|
| 512 | argv[0] = "trace";
|
| 513 | return cmd_trace(argc, argv);
|
| 514 | #endif
|
| 515 | }
|
| 516 |
|
| 517 | argv++;
|
| 518 | argc--;
|
| 519 | handle_options(&argv, &argc, NULL);
|
| 520 | commit_pager_choice();
|
| 521 |
|
| 522 | if (argc > 0) {
|
| 523 | if (strstarts(argv[0], "--"))
|
| 524 | argv[0] += 2;
|
| 525 | } else {
|
| 526 |
|
| 527 | printf("\n usage: %s\n\n", perf_usage_string);
|
| 528 | list_common_cmds_help();
|
| 529 | printf("\n %s\n\n", perf_more_info_string);
|
| 530 | goto out;
|
| 531 | }
|
| 532 | cmd = argv[0];
|
| 533 |
|
| 534 | |
| 535 | * We use PATH to find perf commands, but we prepend some higher
|
| 536 | * precedence paths: the "--exec-path" option, the PERF_EXEC_PATH
|
| 537 | * environment, and the $(perfexecdir) from the Makefile at build
|
| 538 | * time.
|
| 539 |
|
| 540 | setup_path();
|
| 541 | |
| 542 | * Block SIGWINCH notifications so that the thread that wants it can
|
| 543 | * unblock and get syscalls like select interrupted instead of waiting
|
| 544 | * forever while the signal goes to some other non interested thread.
|
| 545 |
|
| 546 | pthread__block_sigwinch();
|
| 547 |
|
| 548 | while (1) {
|
| 549 | run_argv(&argc, &argv);
|
| 550 |
|
| 551 | if (errno != ENOENT)
|
| 552 | break;
|
| 553 |
|
| 554 | if (!done_help) {
|
| 555 | struct cmdnames main_cmds = {};
|
| 556 |
|
| 557 | for (unsigned int i = 0; i < ARRAY_SIZE(commands); i++) {
|
| 558 | add_cmdname(&main_cmds,
|
| 559 | commands[i].cmd,
|
| 560 | strlen(commands[i].cmd));
|
| 561 | }
|
| 562 | cmd = argv[0] = help_unknown_cmd(cmd, &main_cmds);
|
| 563 | clean_cmdnames(&main_cmds);
|
| 564 | done_help = 1;
|
| 565 | if (!cmd)
|
| 566 | break;
|
| 567 | } else
|
| 568 | break;
|
| 569 | }
|
| 570 |
|
| 571 | if (cmd) {
|
| 572 | fprintf(stderr, "Failed to run command '%s': %m\n",
|
| 573 | cmd);
|
| 574 | }
|
| 575 | out:
|
| 576 | if (debug_fp)
|
| 577 | fclose(debug_fp);
|
| 578 |
|
| 579 | return 1;
|
| 580 | }
|