/* * Copyright 2019-2024 OARC, Inc. * Copyright 2017-2018 Akamai Technologies * Copyright 2006-2016 Nominum, Inc. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include "opt.h" #include "log.h" #include "util.h" #include "result.h" #include #include #include #include #include #include #include #include #include #define MAX_OPTS 64 #define LINE_LENGTH 80 typedef struct { char c; perf_opttype_t type; const char* desc; const char* help; const char* defval; char defvalbuf[512]; union { void* valp; char** stringp; bool* boolp; unsigned int* uintp; uint64_t* uint64p; double* doublep; in_port_t* portp; } u; } opt_t; typedef struct long_opt long_opt_t; struct long_opt { long_opt_t* next; const char* name; perf_opttype_t type; const char* desc; const char* help; const char* defval; char defvalbuf[512]; union { void* valp; char** stringp; bool* boolp; unsigned int* uintp; uint64_t* uint64p; double* doublep; in_port_t* portp; } u; }; static opt_t opts[MAX_OPTS]; static long_opt_t* longopts = 0; static unsigned int nopts; static char optstr[MAX_OPTS * 2 + 2 + 1] = { 0 }; extern const char* progname; void perf_opt_add(char c, perf_opttype_t type, const char* desc, const char* help, const char* defval, void* valp) { opt_t* opt; if (nopts == MAX_OPTS) { perf_log_fatal("too many defined options"); return; } opt = &opts[nopts++]; opt->c = c; opt->type = type; opt->desc = desc; opt->help = help; if (defval != NULL) { if (strlen(defval) > sizeof(opt->defvalbuf) - 1) { perf_log_fatal("perf_opt_add(): defval too large"); return; } strncpy(opt->defvalbuf, defval, sizeof(opt->defvalbuf)); opt->defvalbuf[sizeof(opt->defvalbuf) - 1] = 0; opt->defval = opt->defvalbuf; } else { opt->defval = NULL; } opt->u.valp = valp; char newoptstr[sizeof(optstr) + 2]; snprintf(newoptstr, sizeof(newoptstr), "%s%c%s", optstr, c, (type == perf_opt_boolean ? "" : ":")); memcpy(optstr, newoptstr, sizeof(optstr) - 1); optstr[sizeof(optstr) - 1] = 0; } void perf_long_opt_add(const char* name, perf_opttype_t type, const char* desc, const char* help, const char* defval, void* valp) { long_opt_t* opt = calloc(1, sizeof(long_opt_t)); if (!opt) { perf_log_fatal("perf_long_opt_add(): out of memory"); return; } opt->name = name; opt->type = type; opt->desc = desc; opt->help = help; if (defval != NULL) { if (strlen(defval) > sizeof(opt->defvalbuf) - 1) { perf_log_fatal("perf_opt_add(): defval too large"); free(opt); // fix clang scan-build return; } strncpy(opt->defvalbuf, defval, sizeof(opt->defvalbuf)); opt->defvalbuf[sizeof(opt->defvalbuf) - 1] = 0; opt->defval = opt->defvalbuf; } else { opt->defval = NULL; } opt->u.valp = valp; opt->next = longopts; longopts = opt; } void perf_opt_usage(void) { unsigned int prefix_len, position, arg_len, i, j; prefix_len = fprintf(stderr, "Usage: %s", progname); position = prefix_len; for (i = 0; i < nopts; i++) { arg_len = 6; if (opts[i].desc != NULL) arg_len += strlen(opts[i].desc) + 1; if (LINE_LENGTH - position - 1 < arg_len) { fprintf(stderr, "\n"); for (j = 0; j < prefix_len; j++) fprintf(stderr, " "); position = prefix_len; } fprintf(stderr, " [-%c", opts[i].c); if (opts[i].desc != NULL) fprintf(stderr, " %s", opts[i].desc); fprintf(stderr, "]"); position += arg_len; } fprintf(stderr, "\n"); for (i = 0; i < nopts; i++) { fprintf(stderr, " -%c %s", opts[i].c, opts[i].help); if (opts[i].defval) fprintf(stderr, " (default: %s)", opts[i].defval); fprintf(stderr, "\n"); } } static uint32_t parse_uint(const char* desc, const char* str, unsigned int min, unsigned int max) { unsigned long int val; uint32_t ret; char* endptr = 0; errno = 0; val = strtoul(str, &endptr, 10); if (!errno && str && *str && endptr && !*endptr && val <= UINT32_MAX) { ret = (uint32_t)val; if (ret >= min && ret <= max) { return ret; } } fprintf(stderr, "invalid %s: %s\n", desc, str); perf_opt_usage(); exit(1); } static double parse_double(const char* desc, const char* str) { const char* s; char c; bool seen_dot = false; s = str; while (*s != 0) { c = *s++; if (c == '.') { if (seen_dot) goto fail; seen_dot = true; } else if (c < '0' || c > '9') { goto fail; } } return atof(str); fail: fprintf(stderr, "invalid %s: %s\n", desc, str); perf_opt_usage(); exit(1); } static uint64_t parse_timeval(const char* desc, const char* str) { return MILLION * parse_double(desc, str); } static int perf_opt_long_parse(char* optarg) { ssize_t optlen; char* arg; if ((arg = strchr(optarg, '='))) { optlen = arg - optarg; arg++; if (optlen < 1 || !strlen(arg)) { return -1; } } else { optlen = strlen(optarg); } long_opt_t* opt = longopts; while (opt) { if (!strncmp(optarg, opt->name, optlen)) { switch (opt->type) { case perf_opt_string: if (!arg) { return -1; } *opt->u.stringp = arg; break; case perf_opt_boolean: *opt->u.boolp = true; break; case perf_opt_uint: if (!arg) { return -1; } *opt->u.uintp = parse_uint(opt->desc, arg, 1, 0xFFFFFFFF); break; case perf_opt_zpint: if (!arg) { return -1; } *opt->u.uintp = parse_uint(opt->desc, arg, 0, 0xFFFFFFFF); break; case perf_opt_timeval: if (!arg) { return -1; } *opt->u.uint64p = parse_timeval(opt->desc, arg); break; case perf_opt_double: if (!arg) { return -1; } *opt->u.doublep = parse_double(opt->desc, arg); break; case perf_opt_port: if (!arg) { return -1; } *opt->u.portp = parse_uint(opt->desc, arg, 0, 0xFFFF); break; } return 0; } opt = opt->next; } return -1; } void perf_long_opt_usage(void) { fprintf(stderr, "Usage: %s ... -O [=] ...\n\nAvailable long options:\n", progname); long_opt_t* opt = longopts; while (opt) { if (opt->type == perf_opt_boolean) { fprintf(stderr, " %s: %s", opt->name, opt->help); } else { fprintf(stderr, " %s=<%s>: %s", opt->name, opt->desc ? opt->desc : "val", opt->help); } if (opt->defval) { fprintf(stderr, " (default: %s)", opt->defval); } fprintf(stderr, "\n"); opt = opt->next; } } void perf_opt_parse(int argc, char** argv) { int c; opt_t* opt; unsigned int i; perf_opt_add('h', perf_opt_boolean, NULL, "print this help", NULL, NULL); perf_opt_add('H', perf_opt_boolean, NULL, "print long options help", NULL, NULL); perf_opt_add('O', perf_opt_string, NULL, "set long options: =", NULL, NULL); while ((c = getopt(argc, argv, optstr)) != -1) { for (i = 0; i < nopts; i++) { if (opts[i].c == c) break; } if (i == nopts) { perf_opt_usage(); exit(1); } if (c == 'h') { perf_opt_usage(); exit(0); } if (c == 'H') { perf_long_opt_usage(); exit(0); } if (c == 'O') { if (perf_opt_long_parse(optarg)) { fprintf(stderr, "invalid long option: %s\n", optarg); perf_opt_usage(); exit(1); } continue; } opt = &opts[i]; switch (opt->type) { case perf_opt_string: *opt->u.stringp = optarg; break; case perf_opt_boolean: *opt->u.boolp = true; break; case perf_opt_uint: *opt->u.uintp = parse_uint(opt->desc, optarg, 1, 0xFFFFFFFF); break; case perf_opt_zpint: *opt->u.uintp = parse_uint(opt->desc, optarg, 0, 0xFFFFFFFF); break; case perf_opt_timeval: *opt->u.uint64p = parse_timeval(opt->desc, optarg); break; case perf_opt_double: *opt->u.doublep = parse_double(opt->desc, optarg); break; case perf_opt_port: *opt->u.portp = parse_uint(opt->desc, optarg, 0, 0xFFFF); break; } } if (optind != argc) { fprintf(stderr, "unexpected argument %s\n", argv[optind]); perf_opt_usage(); exit(1); } } perf_suppress_t perf_opt_parse_suppress(const char* val) { perf_suppress_t s = { false, false, false, false }; while (val && *val) { const char* next = strchr(val, ','); int len; if (next) { len = next - val; next += 1; } else { len = strlen(val); next = 0; } if (!strncmp(val, "timeouts", len)) { s.timeouts = true; } else if (!strncmp(val, "congestion", len)) { s.congestion = true; } else if (!strncmp(val, "sendfailed", len)) { s.sendfailed = true; } else if (!strncmp(val, "sockready", len)) { s.sockready = true; } else if (!strncmp(val, "unexpected", len)) { s.unexpected = true; } else { fprintf(stderr, "unknown message type to suppress: %.*s\n", len, val); perf_opt_usage(); exit(1); } val = next; } return s; }