Win port: add base::getopt_compat

Windows doesn't have <getopt.h> but all our executable these
days depend on it. We have two roads ahead:
1. Create an abstraction layer, like base::ArgParsers to deal
   with cmdline parsing.
2. Create a getopt emulation for Windows.

1 is too much risk and too much effort. On Android our cmdline
is de-facto an API, we can't risk subtle breakages.
Instead it's easier and lower risk to create a getopt emulation
for Windows.
This CL also introduces a test that checks that the behavior of
the two is consistent.

Bug: 174454879
Test: perfetto_unittests --gtest_filter=GetOpt*

Change-Id: I68752e615ad0aaf24226ddbce0d7ba77140bdca9
diff --git a/Android.bp b/Android.bp
index 046da6e..a8f0d8c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6405,6 +6405,7 @@
     "src/base/ctrl_c_handler.cc",
     "src/base/event_fd.cc",
     "src/base/file_utils.cc",
+    "src/base/getopt_compat.cc",
     "src/base/logging.cc",
     "src/base/metatrace.cc",
     "src/base/paged_memory.cc",
@@ -6446,6 +6447,7 @@
   srcs: [
     "src/base/circular_queue_unittest.cc",
     "src/base/flat_set_unittest.cc",
+    "src/base/getopt_compat_unittest.cc",
     "src/base/metatrace_unittest.cc",
     "src/base/no_destructor_unittest.cc",
     "src/base/optional_unittest.cc",
diff --git a/BUILD b/BUILD
index d91e53f..71a5ef7 100644
--- a/BUILD
+++ b/BUILD
@@ -295,6 +295,8 @@
         "include/perfetto/ext/base/endian.h",
         "include/perfetto/ext/base/event_fd.h",
         "include/perfetto/ext/base/file_utils.h",
+        "include/perfetto/ext/base/getopt.h",
+        "include/perfetto/ext/base/getopt_compat.h",
         "include/perfetto/ext/base/hash.h",
         "include/perfetto/ext/base/metatrace.h",
         "include/perfetto/ext/base/metatrace_events.h",
@@ -578,6 +580,7 @@
         "src/base/ctrl_c_handler.cc",
         "src/base/event_fd.cc",
         "src/base/file_utils.cc",
+        "src/base/getopt_compat.cc",
         "src/base/logging.cc",
         "src/base/metatrace.cc",
         "src/base/paged_memory.cc",
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn
index 15e1799..8cfb32e 100644
--- a/include/perfetto/ext/base/BUILD.gn
+++ b/include/perfetto/ext/base/BUILD.gn
@@ -22,6 +22,8 @@
     "endian.h",
     "event_fd.h",
     "file_utils.h",
+    "getopt.h",
+    "getopt_compat.h",
     "hash.h",
     "metatrace.h",
     "metatrace_events.h",
diff --git a/include/perfetto/ext/base/getopt.h b/include/perfetto/ext/base/getopt.h
new file mode 100644
index 0000000..77c1490
--- /dev/null
+++ b/include/perfetto/ext/base/getopt.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef INCLUDE_PERFETTO_EXT_BASE_GETOPT_H_
+#define INCLUDE_PERFETTO_EXT_BASE_GETOPT_H_
+
+// This is the header that should be included in all places that need getopt.h.
+// This either routes on the sysroot getopt.h, for OSes that have one (all but
+// Windows) or routes on the home-brewed getopt_compat.h.
+
+#include "perfetto/base/build_config.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include "perfetto/ext/base/getopt_compat.h"
+
+// getopt_compat.h puts everything in a nested namespace, to allow
+// getopt_compat_unittest.cc to use both <getopt.h> and "getopt_compat.h"
+// without collisions.
+
+// Here we expose them into the root namespace, because we want offer a drop-in
+// replacement to the various main.cc, which can't know about the nested
+// namespace.
+using ::perfetto::base::getopt_compat::optarg;
+using ::perfetto::base::getopt_compat::optind;
+using ::perfetto::base::getopt_compat::option;
+constexpr auto getopt = ::perfetto::base::getopt_compat::getopt;
+constexpr auto getopt_long = ::perfetto::base::getopt_compat::getopt_long;
+constexpr auto no_argument = ::perfetto::base::getopt_compat::no_argument;
+constexpr auto required_argument =
+    ::perfetto::base::getopt_compat::required_argument;
+
+#else
+#include <getopt.h>
+#endif
+
+#endif  // INCLUDE_PERFETTO_EXT_BASE_GETOPT_H_
diff --git a/include/perfetto/ext/base/getopt_compat.h b/include/perfetto/ext/base/getopt_compat.h
new file mode 100644
index 0000000..d1f5436
--- /dev/null
+++ b/include/perfetto/ext/base/getopt_compat.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef INCLUDE_PERFETTO_EXT_BASE_GETOPT_COMPAT_H_
+#define INCLUDE_PERFETTO_EXT_BASE_GETOPT_COMPAT_H_
+
+#include <cstddef>  // For std::nullptr_t
+
+// No translation units other than base/getopt.h and getopt_compat_unittest.cc
+// should directly include this file. Use base/getopt.h instead.
+
+namespace perfetto {
+namespace base {
+namespace getopt_compat {
+
+// A tiny getopt() replacement for Windows, which doesn't have <getopt.h>.
+// This implementation is based on the subset of features that we use in the
+// Perfetto codebase. It doesn't even try to deal with the full surface of GNU's
+// getopt().
+// Limitations:
+// - getopt_long_only() is not supported.
+// - optional_argument is not supported. That is extremely subtle and caused us
+//   problems in the past with GNU's getopt.
+// - It does not reorder non-option arguments. It behaves like MacOS getopt, or
+//   GNU's when POSIXLY_CORRECT=1.
+// - Doesn't expose optopt or opterr.
+// - option.flag and longindex are not supported and must be nullptr.
+
+enum {
+  no_argument = 0,
+  required_argument = 1,
+};
+
+struct option {
+  const char* name;
+  int has_arg;
+  std::nullptr_t flag;  // Only nullptr is supported.
+  int val;
+};
+
+extern char* optarg;
+extern int optind;
+
+int getopt_long(int argc,
+                char** argv,
+                const char* shortopts,
+                const option* longopts,
+                std::nullptr_t /*longindex is not supported*/);
+
+int getopt(int argc, char** argv, const char* shortopts);
+
+}  // namespace getopt_compat
+}  // namespace base
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_BASE_GETOPT_COMPAT_H_
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 76921cf..8c05e2e 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -33,6 +33,7 @@
     "ctrl_c_handler.cc",
     "event_fd.cc",
     "file_utils.cc",
+    "getopt_compat.cc",
     "logging.cc",
     "metatrace.cc",
     "paged_memory.cc",
@@ -152,6 +153,7 @@
   sources = [
     "circular_queue_unittest.cc",
     "flat_set_unittest.cc",
+    "getopt_compat_unittest.cc",
     "no_destructor_unittest.cc",
     "optional_unittest.cc",
     "paged_memory_unittest.cc",
diff --git a/src/base/getopt_compat.cc b/src/base/getopt_compat.cc
new file mode 100644
index 0000000..19c8adc
--- /dev/null
+++ b/src/base/getopt_compat.cc
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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 "perfetto/ext/base/getopt_compat.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <vector>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace base {
+namespace getopt_compat {
+
+char* optarg = nullptr;
+int optind = 0;
+
+namespace {
+
+char* nextchar = nullptr;
+
+const option* LookupLongOpt(const std::vector<option>& opts,
+                            const char* name,
+                            size_t len) {
+  for (const option& opt : opts) {
+    if (strncmp(opt.name, name, len) == 0 && strlen(opt.name) == len)
+      return &opt;
+  }
+  return nullptr;
+}
+
+const option* LookupShortOpt(const std::vector<option>& opts, char c) {
+  for (const option& opt : opts) {
+    if (opt.name == nullptr && opt.val == c)
+      return &opt;
+  }
+  return nullptr;
+}
+
+bool ParseOpts(const char* shortopts,
+               const option* longopts,
+               std::vector<option>* res) {
+  // Parse long options first.
+  for (const option* lopt = longopts; lopt && lopt->name; lopt++) {
+    PERFETTO_CHECK(lopt->flag == nullptr);
+    PERFETTO_CHECK(lopt->has_arg == no_argument ||
+                   lopt->has_arg == required_argument);
+    res->emplace_back(*lopt);
+  }
+
+  // Merge short options.
+  for (const char* sopt = shortopts; sopt && *sopt;) {
+    const size_t idx = static_cast<size_t>(sopt - shortopts);
+    char c = *sopt++;
+    bool valid = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+                 (c >= '0' && c <= '9');
+    if (!valid) {
+      fprintf(stderr,
+              "Error parsing shortopts. Unexpected char '%c' at offset %zu\n",
+              c, idx);
+      return false;
+    }
+    res->emplace_back();
+    option& opt = res->back();
+    opt.val = c;
+    opt.has_arg = no_argument;
+    if (*sopt == ':') {
+      opt.has_arg = required_argument;
+      ++sopt;
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
+int getopt_long(int argc,
+                char** argv,
+                const char* shortopts,
+                const option* longopts,
+                std::nullptr_t /*longind*/) {
+  std::vector<option> opts;
+  optarg = nullptr;
+
+  if (optind == 0)
+    optind = 1;
+
+  if (optind >= argc)
+    return -1;
+
+  if (!ParseOpts(shortopts, longopts, &opts))
+    return '?';
+
+  char* arg = argv[optind];
+
+  if (!nextchar) {
+    // If |nextchar| is null we are NOT in the middle of a short option and we
+    // should parse the next argv.
+    if (strncmp(arg, "--", 2) == 0 && strlen(arg) > 2) {
+      // A --long option.
+      arg += 2;
+      char* sep = strchr(arg, '=');
+      optind++;
+
+      size_t len = sep ? static_cast<size_t>(sep - arg) : strlen(arg);
+      const option* opt = LookupLongOpt(opts, arg, len);
+      if (!opt) {
+        fprintf(stderr, "unrecognized option '--%s'\n", arg);
+        return '?';
+      }
+
+      if (opt->has_arg == no_argument) {
+        if (sep) {
+          fprintf(stderr, "option '--%s' doesn't allow an argument\n", arg);
+          return '?';
+        } else {
+          return opt->val;
+        }
+      } else if (opt->has_arg == required_argument) {
+        if (sep) {
+          optarg = sep + 1;
+          return opt->val;
+        } else if (optind >= argc) {
+          fprintf(stderr, "option '--%s' requires an argument\n", arg);
+          return '?';
+        } else {
+          optarg = argv[optind++];
+          return opt->val;
+        }
+      }
+      // has_arg must be either |no_argument| or |required_argument|. We
+      // shoulnd't get here unless the check in ParseOpts() has a bug.
+      PERFETTO_CHECK(false);
+    }  // if (arg ~= "--*").
+
+    if (strlen(arg) > 1 && arg[0] == '-' && arg[1] != '-') {
+      // A sequence of short options. Parsing logic continues below.
+      nextchar = &arg[1];
+    }
+  }  // if(!nextchar)
+
+  if (nextchar) {
+    // At this point either:
+    // 1. This is the first char of a sequence of short options, and we fell
+    //    through here from the lines above.
+    // 2. This is the N (>1) char of a sequence of short options, and we got
+    //    here from a new getopt() call to getopt().
+    const char cur_char = *nextchar;
+    PERFETTO_CHECK(cur_char != '\0');
+
+    // Advance the option char in any case, before we start reasoning on them.
+    // if we got to the end of the "-abc" sequence, increment optind so the next
+    // getopt() call resumes from the next argv argument.
+    if (*(++nextchar) == '\0') {
+      nextchar = nullptr;
+      ++optind;
+    }
+
+    const option* opt = LookupShortOpt(opts, cur_char);
+    if (!opt) {
+      fprintf(stderr, "invalid option -- '%c'\n", cur_char);
+      return '?';
+    }
+    if (opt->has_arg == no_argument) {
+      return cur_char;
+    } else if (opt->has_arg == required_argument) {
+      // This is a subtle getopt behavior. Say you call `tar -fx`, there are
+      // two cases:
+      // 1. If 'f' is no_argument then 'x' (and anything else after) is
+      //    interpreted as an independent argument (like `tar -f -x`).
+      // 2. If 'f' is required_argument, than everything else after the 'f'
+      //    is interpreted as the option argument (like `tar -f x`)
+      if (!nextchar) {
+        // Case 1.
+        if (optind >= argc) {
+          fprintf(stderr, "option requires an argument -- '%c'\n", cur_char);
+          return '?';
+        } else {
+          optarg = argv[optind++];
+          return cur_char;
+        }
+      } else {
+        // Case 2.
+        optarg = nextchar;
+        nextchar = nullptr;
+        optind++;
+        return cur_char;
+      }
+    }
+    PERFETTO_CHECK(false);
+  }  // if (nextchar)
+
+  // If we get here, we found the first non-option argument. Stop here.
+
+  if (strcmp(arg, "--") == 0)
+    optind++;
+
+  return -1;
+}
+
+int getopt(int argc, char** argv, const char* shortopts) {
+  return getopt_long(argc, argv, shortopts, nullptr, nullptr);
+}
+
+}  // namespace getopt_compat
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/base/getopt_compat_unittest.cc b/src/base/getopt_compat_unittest.cc
new file mode 100644
index 0000000..a7d4db3
--- /dev/null
+++ b/src/base/getopt_compat_unittest.cc
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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 "perfetto/ext/base/getopt_compat.h"
+
+// This test has two roles:
+// 1. In Windows builds it's a plain unittest for our getopt_compat.cc
+// 2. On other builds it also checks that the behavior of our getopt_compat.cc
+//    is the same of <getopt.h> (for the options we support).
+// It does so creating a gtest typed test, and defining two structs that inject
+// getopt functions and global variables like optind.
+
+#include "perfetto/base/build_config.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <getopt.h>
+#endif
+
+#include <initializer_list>
+
+#include "test/gtest_and_gmock.h"
+
+using testing::ElementsAre;
+using testing::ElementsAreArray;
+
+namespace perfetto {
+namespace base {
+namespace {
+
+struct OurGetopt {
+  using LongOptionType = getopt_compat::option;
+  using GetoptFn = decltype(&getopt_compat::getopt);
+  using GetoptLongFn = decltype(&getopt_compat::getopt_long);
+  GetoptFn getopt = &getopt_compat::getopt;
+  GetoptLongFn getopt_long = &getopt_compat::getopt_long;
+  int& optind = getopt_compat::optind;
+  char*& optarg = getopt_compat::optarg;
+};
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+struct SystemGetopt {
+  using LongOptionType = ::option;
+  using GetoptFn = decltype(&::getopt);
+  using GetoptLongFn = decltype(&::getopt_long);
+  GetoptFn getopt = &::getopt;
+  GetoptLongFn getopt_long = &::getopt_long;
+  int& optind = ::optind;
+  char*& optarg = ::optarg;
+};
+#endif
+
+template <typename T>
+class GetoptCompatTest : public testing::Test {
+ public:
+  inline void SetCmdline(std::initializer_list<const char*> arg_list) {
+    // Reset the getopt() state.
+    // When calling getopt() several times, MacOS requires that optind is reset
+    // to 1, while Linux requires optind to be reset to 0. Also MacOS requires
+    // optreset to be set as well.
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+    impl.optind = 1;
+    optreset = 1;  // It has no corresponding variable in other OSes.
+#else
+    impl.optind = 0;
+#endif
+    argc = static_cast<int>(arg_list.size());
+    for (char*& arg : argv)
+      arg = nullptr;
+    size_t i = 0;
+    for (const char* arg : arg_list)
+      argv[i++] = const_cast<char*>(arg);
+  }
+  int argc;
+  char* argv[32];  // We don't use more than 32 entries on our tests.
+  T impl;
+};
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+using GetoptTestTypes = ::testing::Types<OurGetopt>;
+#else
+using GetoptTestTypes = ::testing::Types<OurGetopt, SystemGetopt>;
+#endif
+TYPED_TEST_SUITE(GetoptCompatTest, GetoptTestTypes, /* trailing ',' for GCC*/);
+
+TYPED_TEST(GetoptCompatTest, ShortOptions) {
+  auto& t = this->impl;
+
+  const char* sops = "";
+  this->SetCmdline({"argv0"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+
+  sops = "h";
+  this->SetCmdline({"argv0"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+
+  sops = "h";
+  this->SetCmdline({"argv0", "-h"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'h');
+  EXPECT_EQ(t.optarg, nullptr);
+  EXPECT_EQ(t.optind, 2);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+  EXPECT_EQ(t.optind, 2);
+
+  sops = "h";
+  this->SetCmdline({"argv0", "positional1", "positional2"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+
+  sops = "h";
+  this->SetCmdline({"argv0", "--", "positional1", "positional2"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+  EXPECT_EQ(t.optind, 2);
+
+  sops = "h";
+  this->SetCmdline({"argv0", "-h"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'h');
+  EXPECT_EQ(t.optind, 2);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+  EXPECT_EQ(t.optind, 2);
+
+  sops = "abc";
+  this->SetCmdline({"argv0", "-c", "-a", "-b"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+  EXPECT_EQ(t.optarg, nullptr);
+  EXPECT_EQ(t.optind, 2);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+  EXPECT_EQ(t.optind, 3);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
+  EXPECT_EQ(t.optind, 4);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+  EXPECT_EQ(t.optind, 4);
+
+  sops = "abc";
+  this->SetCmdline({"argv0", "-c", "-a", "--", "nonopt"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+  EXPECT_EQ(t.optarg, nullptr);
+  EXPECT_EQ(t.optind, 2);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+  EXPECT_EQ(t.optarg, nullptr);
+  EXPECT_EQ(t.optind, 3);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+  EXPECT_EQ(t.optarg, nullptr);
+  EXPECT_EQ(t.optind, 4);
+
+  sops = "abc";
+  this->SetCmdline({"argv0", "-cb"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+  EXPECT_EQ(t.optarg, nullptr);
+  EXPECT_EQ(t.optind, 1);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
+  EXPECT_EQ(t.optarg, nullptr);
+  EXPECT_EQ(t.optind, 2);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+  EXPECT_EQ(t.optarg, nullptr);
+  EXPECT_EQ(t.optind, 2);
+
+  sops = "abc";
+  this->SetCmdline({"argv0", "-aa", "-c"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+  EXPECT_EQ(t.optind, 1);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+  EXPECT_EQ(t.optind, 2);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+  EXPECT_EQ(t.optind, 3);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+  EXPECT_EQ(t.optind, 3);
+
+  sops = "a:bc";
+  // The semantic here is `-a b -c`
+  this->SetCmdline({"argv0", "-ab", "-c"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+  EXPECT_EQ(t.optind, 2);
+  EXPECT_STREQ(t.optarg, "b");
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+  EXPECT_EQ(t.optind, 3);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+  EXPECT_EQ(t.optind, 3);
+
+  sops = "a:bc";
+  this->SetCmdline({"argv0", "-ab", "--", "-c"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+  EXPECT_EQ(t.optind, 2);
+  EXPECT_STREQ(t.optarg, "b");
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+  EXPECT_EQ(t.optind, 3);
+
+  sops = "a:b:c:";
+  this->SetCmdline({"argv0", "-a", "arg1", "-b", "--", "-c", "-carg"});
+  // This is sbutle, the "--" is an arg value for "-b", not a separator.
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+  EXPECT_STREQ(t.optarg, "arg1");
+  EXPECT_EQ(t.optind, 3);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
+  EXPECT_STREQ(t.optarg, "--");
+  EXPECT_EQ(t.optind, 5);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+  EXPECT_STREQ(t.optarg, "-carg");
+  EXPECT_EQ(t.optind, 7);
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+  EXPECT_EQ(t.optind, 7);
+
+  sops = "a";
+  this->SetCmdline({"argv0", "-q"});
+  EXPECT_EQ(t.getopt(this->argc, this->argv, sops), '?');
+  EXPECT_EQ(t.optind, 2);
+}
+
+TYPED_TEST(GetoptCompatTest, LongOptions) {
+  auto& t = this->impl;
+  using LongOptionType = typename decltype(this->impl)::LongOptionType;
+
+  {
+    LongOptionType lopts[]{
+        {nullptr, 0, nullptr, 0},
+    };
+    const char* sops = "";
+    this->SetCmdline({"argv0"});
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+    EXPECT_EQ(t.optarg, nullptr);
+    EXPECT_EQ(t.optind, 1);
+  }
+
+  {
+    LongOptionType lopts[]{
+        {nullptr, 0, nullptr, 0},
+    };
+    const char* sops = "";
+    this->SetCmdline({"argv0", "--unknown"});
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?');
+    EXPECT_EQ(t.optarg, nullptr);
+    EXPECT_EQ(t.optind, 2);
+  }
+
+  {
+    LongOptionType lopts[]{
+        {"one", 0 /*no_argument*/, nullptr, 1},
+        {"two", 0 /*no_argument*/, nullptr, 2},
+        {nullptr, 0, nullptr, 0},
+    };
+    const char* sops = "";
+    this->SetCmdline({"argv0", "--two", "--one"});
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+    EXPECT_EQ(t.optarg, nullptr);
+    EXPECT_EQ(t.optind, 2);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
+    EXPECT_EQ(t.optarg, nullptr);
+    EXPECT_EQ(t.optind, 3);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+    EXPECT_EQ(t.optarg, nullptr);
+    EXPECT_EQ(t.optind, 3);
+  }
+
+  {
+    LongOptionType lopts[]{
+        {"one", 0 /*no_argument*/, nullptr, 1},
+        {"two", 0 /*no_argument*/, nullptr, 2},
+        {nullptr, 0, nullptr, 0},
+    };
+    const char* sops = "";
+    this->SetCmdline({"argv0", "--two", "--one", "--not-an-opt"});
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+    EXPECT_EQ(t.optarg, nullptr);
+    EXPECT_EQ(t.optind, 2);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
+    EXPECT_EQ(t.optarg, nullptr);
+    EXPECT_EQ(t.optind, 3);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?');
+    EXPECT_EQ(t.optarg, nullptr);
+    EXPECT_EQ(t.optind, 4);
+  }
+
+  {
+    LongOptionType lopts[]{
+        {"one", 0 /*no_argument*/, nullptr, 1},
+        {"two", 0 /*no_argument*/, nullptr, 2},
+        {nullptr, 0, nullptr, 0},
+    };
+    const char* sops = "";
+    this->SetCmdline({"argv0", "--two", "--one", "--", "--not-an-opt"});
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+    EXPECT_EQ(t.optind, 2);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
+    EXPECT_EQ(t.optind, 3);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+    EXPECT_EQ(t.optind, 4);
+  }
+
+  {
+    LongOptionType lopts[]{
+        {"no1", 0 /*no_argument*/, nullptr, 1},
+        {"req2", 1 /*required_argument*/, nullptr, 2},
+        {"req3", 1 /*required_argument*/, nullptr, 3},
+        {nullptr, 0, nullptr, 0},
+    };
+    const char* sops = "";
+    // This is subtle: the "--" really is an argument for req2, not an argument
+    // separator. The first positional arg is "!!!".
+    this->SetCmdline({"argv0", "--req3", "-", "--no1", "--req2", "--", "!!!"});
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 3);
+    EXPECT_EQ(t.optind, 3);
+    EXPECT_STREQ(t.optarg, "-");
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
+    EXPECT_EQ(t.optind, 4);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+    EXPECT_STREQ(t.optarg, "--");
+    EXPECT_EQ(t.optind, 6);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+    EXPECT_EQ(t.optind, 6);
+  }
+
+  {
+    LongOptionType lopts[]{
+        {"no1", 0 /*no_argument*/, nullptr, 1},
+        {"req2", 1 /*required_argument*/, nullptr, 2},
+        {nullptr, 0, nullptr, 0},
+    };
+    const char* sops = "";
+    this->SetCmdline({"argv0", "--req2", "foo", "--", "--no1"});
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+    EXPECT_EQ(t.optind, 3);
+    EXPECT_STREQ(t.optarg, "foo");
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+    EXPECT_EQ(t.optind, 4);
+  }
+}
+
+TYPED_TEST(GetoptCompatTest, ShortAndLongOptions) {
+  auto& t = this->impl;
+  using LongOptionType = typename decltype(this->impl)::LongOptionType;
+
+  {
+    LongOptionType lopts[]{
+        {"one", 0 /*no_argument*/, nullptr, 1},
+        {"two", 0 /*no_argument*/, nullptr, 2},
+        {"three", 0 /*no_argument*/, nullptr, 3},
+        {nullptr, 0, nullptr, 0},
+    };
+    const char* sops = "123";
+
+    this->SetCmdline({"argv0"});
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+    EXPECT_EQ(t.optarg, nullptr);
+    EXPECT_EQ(t.optind, 1);
+
+    this->SetCmdline({"argv0", "-13", "--two", "--three", "--", "--one"});
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
+    EXPECT_EQ(t.optind, 1);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '3');
+    EXPECT_EQ(t.optind, 2);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+    EXPECT_EQ(t.optind, 3);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 3);
+    EXPECT_EQ(t.optind, 4);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+    EXPECT_EQ(t.optind, 5);
+
+    this->SetCmdline({"argv0", "--two", "-1", "--two", "-13"});
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+    EXPECT_EQ(t.optind, 2);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
+    EXPECT_EQ(t.optind, 3);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+    EXPECT_EQ(t.optind, 4);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
+    EXPECT_EQ(t.optind, 4);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '3');
+    EXPECT_EQ(t.optind, 5);
+    EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+    EXPECT_EQ(t.optind, 5);
+  }
+}
+
+}  // namespace
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/perfetto_cmd/packet_writer.cc b/src/perfetto_cmd/packet_writer.cc
index d4e9835..2bee2f0 100644
--- a/src/perfetto_cmd/packet_writer.cc
+++ b/src/perfetto_cmd/packet_writer.cc
@@ -19,12 +19,12 @@
 #include <array>
 
 #include <fcntl.h>
-#include <getopt.h>
 #include <signal.h>
 #include <stdio.h>
 #include <sys/stat.h>
 
 #include "perfetto/base/build_config.h"
+#include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/paged_memory.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/tracing/core/trace_packet.h"
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 2610154..5890abe 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -19,7 +19,6 @@
 #include "perfetto/base/build_config.h"
 
 #include <fcntl.h>
-#include <getopt.h>
 #include <stdio.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -46,6 +45,7 @@
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/ctrl_c_handler.h"
 #include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/thread_utils.h"
 #include "perfetto/ext/base/utils.h"
@@ -265,7 +265,6 @@
       {"save-for-bugreport", no_argument, nullptr, OPT_BUGREPORT},
       {nullptr, 0, nullptr, 0}};
 
-  int option_index = 0;
   std::string config_file_name;
   std::string trace_config_raw;
   bool background = false;
@@ -278,8 +277,7 @@
   bool has_config_options = false;
 
   for (;;) {
-    int option =
-        getopt_long(argc, argv, "hc:o:dt:b:s:", long_options, &option_index);
+    int option = getopt_long(argc, argv, "hc:o:dt:b:s:", long_options, nullptr);
 
     if (option == -1)
       break;  // EOF.
diff --git a/src/perfetto_cmd/trigger_perfetto.cc b/src/perfetto_cmd/trigger_perfetto.cc
index 2e88412..ae44018 100644
--- a/src/perfetto_cmd/trigger_perfetto.cc
+++ b/src/perfetto_cmd/trigger_perfetto.cc
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-#include <getopt.h>
-
 #include <string>
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/unix_task_runner.h"
 #include "perfetto/ext/traced/traced.h"
 #include "src/android_stats/statsd_logging_helper.h"
@@ -43,12 +42,10 @@
   static const option long_options[] = {{"help", no_argument, nullptr, 'h'},
                                         {nullptr, 0, nullptr, 0}};
 
-  int option_index = 0;
-
   std::vector<std::string> triggers_to_activate;
 
   for (;;) {
-    int option = getopt_long(argc, argv, "h", long_options, &option_index);
+    int option = getopt_long(argc, argv, "h", long_options, nullptr);
 
     if (option == 'h')
       return PrintUsage(argv[0]);
diff --git a/src/profiling/memory/main.cc b/src/profiling/memory/main.cc
index cfc4943..146e98d 100644
--- a/src/profiling/memory/main.cc
+++ b/src/profiling/memory/main.cc
@@ -20,10 +20,10 @@
 #include <memory>
 #include <vector>
 
-#include <getopt.h>
 #include <signal.h>
 
 #include "perfetto/ext/base/event_fd.h"
+#include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/unix_socket.h"
 #include "perfetto/ext/base/watchdog.h"
@@ -73,9 +73,8 @@
       {"exclusive-for-cmdline", required_argument, nullptr, kTargetCmd},
       {"inherit-socket-fd", required_argument, nullptr, kInheritFd},
       {nullptr, 0, nullptr, 0}};
-  int option_index;
   int c;
-  while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+  while ((c = getopt_long(argc, argv, "", long_options, nullptr)) != -1) {
     switch (c) {
       case kCleanupCrash:
         cleanup_crash = true;
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index cb6a17e..e02225d 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -32,6 +32,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
@@ -75,7 +76,6 @@
 #define ftruncate _chsize
 #else
 #include <dirent.h>
-#include <getopt.h>
 #endif
 
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE) && \
@@ -668,63 +668,6 @@
   std::string metatrace_path;
 };
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-void PrintUsage(char** argv) {
-  PERFETTO_ELOG(R"(
-Interactive trace processor shell.
-Usage: %s [OPTIONS] trace_file.pb
-
-Options:
- -q, --query-file FILE                Read and execute an SQL query from a file.
-                                      If used with --run-metrics, the query is
-                                      executed after the selected metrics and
-                                      the metrics output is suppressed.
- --pre-metrics FILE                   Read and execute an SQL query from a file.
-                                      This query is executed before the selected
-                                      metrics and can't output any results.
- --run-metrics x,y,z                  Runs a comma separated list of metrics and
-                                      prints the result as a TraceMetrics proto
-                                      to stdout. The specified can either be
-                                      in-built metrics or SQL/proto files of
-                                      extension metrics.
- --metrics-output [binary|text|json]  Allows the output of --run-metrics to be
-                                      specified in either proto binary, proto
-                                      text format or JSON format (default: proto
-                                      text).)",
-                argv[0]);
-}
-
-CommandLineOptions ParseCommandLineOptions(int argc, char** argv) {
-  CommandLineOptions command_line_options;
-
-  if (argc < 2 || argc % 2 == 1) {
-    PrintUsage(argv);
-    exit(1);
-  }
-
-  for (int i = 1; i < argc - 1; i += 2) {
-    if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--query-file") == 0) {
-      command_line_options.query_file_path = argv[i + 1];
-    } else if (strcmp(argv[i], "--pre-metrics") == 0) {
-      command_line_options.pre_metrics_path = argv[i + 1];
-    } else if (strcmp(argv[i], "--run-metrics") == 0) {
-      command_line_options.metric_names = argv[i + 1];
-    } else if (strcmp(argv[i], "--metrics-output") == 0) {
-      command_line_options.metric_output = argv[i + 1];
-    } else {
-      PrintUsage(argv);
-      exit(1);
-    }
-  }
-  command_line_options.trace_file_path = argv[argc - 1];
-  command_line_options.launch_shell =
-      command_line_options.metric_names.empty() &&
-      command_line_options.query_file_path.empty();
-  return command_line_options;
-}
-
-#else  // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-
 void PrintUsage(char** argv) {
   PERFETTO_ELOG(R"(
 Interactive trace processor shell.
@@ -802,10 +745,9 @@
       {nullptr, 0, nullptr, 0}};
 
   bool explicit_interactive = false;
-  int option_index = 0;
   for (;;) {
     int option =
-        getopt_long(argc, argv, "hvWiDdm:p:q:e:", long_options, &option_index);
+        getopt_long(argc, argv, "hvWiDdm:p:q:e:", long_options, nullptr);
 
     if (option == -1)
       break;  // EOF.
@@ -913,8 +855,6 @@
   return command_line_options;
 }
 
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-
 void ExtendPoolWithBinaryDescriptor(google::protobuf::DescriptorPool& pool,
                                     const void* data,
                                     int size) {
diff --git a/src/traced/probes/probes.cc b/src/traced/probes/probes.cc
index 5e8a187..9d83f45 100644
--- a/src/traced/probes/probes.cc
+++ b/src/traced/probes/probes.cc
@@ -15,12 +15,12 @@
  */
 
 #include <fcntl.h>
-#include <getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/unix_task_runner.h"
 #include "perfetto/ext/base/version.h"
 #include "perfetto/ext/traced/traced.h"
@@ -42,9 +42,8 @@
       {"version", no_argument, nullptr, OPT_VERSION},
       {nullptr, 0, nullptr, 0}};
 
-  int option_index;
   for (;;) {
-    int option = getopt_long(argc, argv, "", long_options, &option_index);
+    int option = getopt_long(argc, argv, "", long_options, nullptr);
     if (option == -1)
       break;
     switch (option) {
diff --git a/src/traced/service/service.cc b/src/traced/service/service.cc
index 69d5a1c..769ce05 100644
--- a/src/traced/service/service.cc
+++ b/src/traced/service/service.cc
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include <getopt.h>
 #include <stdio.h>
 #include <algorithm>
 
+#include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/unix_task_runner.h"
 #include "perfetto/ext/base/version.h"
@@ -109,9 +109,8 @@
   std::string producer_socket_group, consumer_socket_group,
       producer_socket_mode, consumer_socket_mode;
 
-  int option_index;
   for (;;) {
-    int option = getopt_long(argc, argv, "", long_options, &option_index);
+    int option = getopt_long(argc, argv, "", long_options, nullptr);
     if (option == -1)
       break;
     switch (option) {
diff --git a/tools/busy_threads/busy_threads.cc b/tools/busy_threads/busy_threads.cc
index d7976ed..56a80e2 100644
--- a/tools/busy_threads/busy_threads.cc
+++ b/tools/busy_threads/busy_threads.cc
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <getopt.h>
 #include <inttypes.h>
 #include <stdint.h>
 #include <unistd.h>
@@ -24,6 +23,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/scoped_file.h"
 
 #define PERFETTO_HAVE_PTHREADS                \
@@ -111,9 +111,8 @@
 #endif
     {nullptr, 0, nullptr, 0}
   };
-  int option_index;
   int c;
-  while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+  while ((c = getopt_long(argc, argv, "", long_options, nullptr)) != -1) {
     switch (c) {
       case 'd':
         background = true;
diff --git a/tools/cpu_utilization/cpu_utilization.cc b/tools/cpu_utilization/cpu_utilization.cc
index 9dc1ddd..2aea7ba 100644
--- a/tools/cpu_utilization/cpu_utilization.cc
+++ b/tools/cpu_utilization/cpu_utilization.cc
@@ -15,7 +15,6 @@
  */
 
 #include <fcntl.h>
-#include <getopt.h>
 #include <inttypes.h>
 #include <stdint.h>
 #include <sys/stat.h>
@@ -28,6 +27,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/scoped_file.h"
 
 // Periodically prints an un-normalized cpu usage ratio (full use of a single
@@ -72,9 +72,8 @@
       {"sleep-duration-us", required_argument, nullptr, 't'},
       {"sleep-intervals", required_argument, nullptr, 'n'},
       {nullptr, 0, nullptr, 0}};
-  int option_index;
   int c;
-  while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+  while ((c = getopt_long(argc, argv, "", long_options, nullptr)) != -1) {
     switch (c) {
       case 'p':
         target_pid = atoi(optarg);
diff --git a/tools/ftrace_proto_gen/main.cc b/tools/ftrace_proto_gen/main.cc
index a92505c..6fca8ce 100644
--- a/tools/ftrace_proto_gen/main.cc
+++ b/tools/ftrace_proto_gen/main.cc
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <getopt.h>
 #include <sys/stat.h>
 #include <fstream>
 #include <map>
@@ -29,6 +28,7 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/getopt.h"
 #include "src/traced/probes/ftrace/format_parser/format_parser.h"
 #include "tools/ftrace_proto_gen/ftrace_descriptor_gen.h"
 #include "tools/ftrace_proto_gen/ftrace_proto_gen.h"
@@ -60,7 +60,6 @@
       {"check_only", no_argument, nullptr, 'c'},
       {nullptr, 0, nullptr, 0}};
 
-  int option_index;
   int c;
 
   std::string event_list_path;
@@ -71,7 +70,7 @@
   std::unique_ptr<std::ostream> (*ostream_factory)(const std::string&) =
       &MakeOFStream;
 
-  while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+  while ((c = getopt_long(argc, argv, "", long_options, nullptr)) != -1) {
     switch (c) {
       case 'w':
         event_list_path = optarg;
diff --git a/tools/multithreaded_alloc.cc b/tools/multithreaded_alloc.cc
index 174fd25..ae01aaf 100644
--- a/tools/multithreaded_alloc.cc
+++ b/tools/multithreaded_alloc.cc
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <getopt.h>
 #include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -29,6 +28,7 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/time.h"
+#include "perfetto/ext/base/getopt.h"
 #include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/profiling/memory/heap_profile.h"