text: // Copyright (c) 2014 GitHub, Inc.
  // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by the MIT license that can be
  // found in the LICENSE file.
  
  // For linux_syscall_support.h. This makes it safe to call embedded system
  // calls when in seccomp mode.
  
  #include "atom/common/crash_reporter/linux/crash_dump_handler.h"
  
  #include <poll.h>
  
  #include <algorithm>
  
  #include "base/posix/eintr_wrapper.h"
  #include "vendor/breakpad/src/client/linux/minidump_writer/directory_reader.h"
  #include "vendor/breakpad/src/common/linux/linux_libc_support.h"
  #include "vendor/breakpad/src/common/memory.h"
  
  #include "third_party/lss/linux_syscall_support.h"
  
  // Some versions of gcc are prone to warn about unused return values. In cases
  // where we either a) know the call cannot fail, or b) there is nothing we
  // can do when a call fails, we mark the return code as ignored. This avoids
  // spurious compiler warnings.
  #define IGNORE_RET(x) \
    do {                \
      if (x)            \
        ;               \
    } while (0)
  
  namespace crash_reporter {
  
  namespace {
  
  // String buffer size to use to convert a uint64_t to string.
  const size_t kUint64StringSize = 21;
  
  // Writes the value |v| as 16 hex characters to the memory pointed at by
  // |output|.
  void write_uint64_hex(char* output, uint64_t v) {
    static const char hextable[] = "0123456789abcdef";
  
    for (int i = 15; i >= 0; --i) {
      output[i] = hextable[v & 15];
      v >>= 4;
    }
  }
  
  // uint64_t version of my_int_len() from
  // breakpad/src/common/linux/linux_libc_support.h. Return the length of the
  // given, non-negative integer when expressed in base 10.
  unsigned my_uint64_len(uint64_t i) {
    if (!i)
      return 1;
  
    unsigned len = 0;
    while (i) {
      len++;
      i /= 10;
    }
  
    return len;
  }
  
  // uint64_t version of my_uitos() from
  // breakpad/src/common/linux/linux_libc_support.h. Convert a non-negative
  // integer to a string (not null-terminated).
  void my_uint64tos(char* output, uint64_t i, unsigned i_len) {
    for (unsigned index = i_len; index; --index, i /= 10)
      output[index - 1] = '0' + (i % 10);
  }
  
  // Converts a struct timeval to milliseconds.
  uint64_t kernel_timeval_to_ms(struct kernel_timeval* tv) {
    uint64_t ret = tv->tv_sec;  // Avoid overflow by explicitly using a uint64_t.
    ret *= 1000;
    ret += tv->tv_usec / 1000;
    return ret;
  }
  
  bool my_isxdigit(char c) {
    return (c >= '0' && c <= '9') || ((c | 0x20) >= 'a' && (c | 0x20) <= 'f');
  }
  
  size_t LengthWithoutTrailingSpaces(const char* str, size_t len) {
    while (len > 0 && str[len - 1] == ' ') {
      len--;
    }
    return len;
  }
  
  // MIME substrings.
  const char g_rn[] = "\r\n";
  const char g_form_data_msg[] = "Content-Disposition: form-data; name=\"";
  const char g_quote_msg[] = "\"";
  const char g_dashdash_msg[] = "--";
  const char g_dump_msg[] = "upload_file_minidump\"; filename=\"dump\"";
  const char g_content_type_msg[] = "Content-Type: application/octet-stream";
  
  // MimeWriter manages an iovec for writing MIMEs to a file.
  class MimeWriter {
   public:
    static const int kIovCapacity = 30;
    static const size_t kMaxCrashChunkSize = 64;
  
    MimeWriter(int fd, const char* const mime_boundary);
    ~MimeWriter();
  
    // Append boundary.
    virtual void AddBoundary();
  
    // Append end of file boundary.
    virtual void AddEnd();
  
    // Append key/value pair with specified sizes.
    virtual void AddPairData(const char* msg_type,
                             size_t msg_type_size,
                             const char* msg_data,
                             size_t msg_data_size);
  
    // Append key/value pair.
    void AddPairString(const char* msg_type, const char* msg_data) {
      AddPairData(msg_type, my_strlen(msg_type), msg_data, my_strlen(msg_data));
    }
  
    // Append key/value pair, splitting value into chunks no larger than
    // |chunk_size|. |chunk_size| cannot be greater than |kMaxCrashChunkSize|.
    // The msg_type string will have a counter suffix to distinguish each chunk.
    virtual void AddPairDataInChunks(const char* msg_type,
                                     size_t msg_type_size,
                                     const char* msg_data,
                                     size_t msg_data_size,
                                     size_t chunk_size,
                                     bool strip_trailing_spaces);
  
    // Add binary file contents to be uploaded with the specified filename.
    virtual void AddFileContents(const char* filename_msg,
                                 uint8_t* file_data,
                                 size_t file_size);
  
    // Flush any pending iovecs to the output file.
    void Flush() {
      IGNORE_RET(sys_writev(fd_, iov_, iov_index_));
      iov_index_ = 0;
    }
  
   protected:
    void AddItem(const void* base, size_t size);
    // Minor performance trade-off for easier-to-maintain code.
    void AddString(const char* str) { AddItem(str, my_strlen(str)); }
    void AddItemWithoutTrailingSpaces(const void* base, size_t size);
  
    struct kernel_iovec iov_[kIovCapacity];
    int iov_index_;
  
    // Output file descriptor.
    int fd_;
  
    const char* const mime_boundary_;
  
   private:
    DISALLOW_COPY_AND_ASSIGN(MimeWriter);
  };
  
  MimeWriter::MimeWriter(int fd, const char* const mime_boundary)
      : iov_index_(0), fd_(fd), mime_boundary_(mime_boundary) {}
  
  MimeWriter::~MimeWriter() {}
  
  void MimeWriter::AddBoundary() {
    AddString(mime_boundary_);
    AddString(g_rn);
  }
  
  void MimeWriter::AddEnd() {
    AddString(mime_boundary_);
    AddString(g_dashdash_msg);
    AddString(g_rn);
  }
  
  void MimeWriter::AddPairData(const char* msg_type,
                               size_t msg_type_size,
                               const char* msg_data,
                               size_t msg_data_size) {
    AddString(g_form_data_msg);
    AddItem(msg_type, msg_type_size);
    AddString(g_quote_msg);
    AddString(g_rn);
    AddString(g_rn);
    AddItem(msg_data, msg_data_size);
    AddString(g_rn);
  }
  
  void MimeWriter::AddPairDataInChunks(const char* msg_type,
                                       size_t msg_type_size,
                                       const char* msg_data,
                                       size_t msg_data_size,
                                       size_t chunk_size,
                                       bool strip_trailing_spaces) {
    if (chunk_size > kMaxCrashChunkSize)
      return;
  
    unsigned i = 0;
    size_t done = 0, msg_length = msg_data_size;
  
    while (msg_length) {
      char num[kUint64StringSize];
      const unsigned num_len = my_uint_len(++i);
      my_uitos(num, i, num_len);
  
      size_t chunk_len = std::min(chunk_size, msg_length);
  
      AddString(g_form_data_msg);
      AddItem(msg_type, msg_type_size);
      AddItem(num, num_len);
      AddString(g_quote_msg);
      AddString(g_rn);
      AddString(g_rn);
      if (strip_trailing_spaces) {
        AddItemWithoutTrailingSpaces(msg_data + done, chunk_len);
      } else {
        AddItem(msg_data + done, chunk_len);
      }
      AddString(g_rn);
      AddBoundary();
      Flush();
  
      done += chunk_len;
      msg_length -= chunk_len;
    }
  }
  
  void MimeWriter::AddFileContents(const char* filename_msg,
                                   uint8_t* file_data,
                                   size_t file_size) {
    AddString(g_form_data_msg);
    AddString(filename_msg);
    AddString(g_rn);
    AddString(g_content_type_msg);
    AddString(g_rn);
    AddString(g_rn);
    AddItem(file_data, file_size);
    AddString(g_rn);
  }
  
  void MimeWriter::AddItem(const void* base, size_t size) {
    // Check if the iovec is full and needs to be flushed to output file.
    if (iov_index_ == kIovCapacity) {
      Flush();
    }
    iov_[iov_index_].iov_base = const_cast<void*>(base);
    iov_[iov_index_].iov_len = size;
    ++iov_index_;
  }
  
  void MimeWriter::AddItemWithoutTrailingSpaces(const void* base, size_t size) {
    AddItem(base,
            LengthWithoutTrailingSpaces(static_cast<const char*>(base), size));
  }
  
  void LoadDataFromFD(google_breakpad::PageAllocator* allocator,
                      int fd,
                      bool close_fd,
                      uint8_t** file_data,
                      size_t* size) {
    struct kernel_stat st;
    if (sys_fstat(fd, &st) != 0) {
      static const char msg[] = "Cannot upload crash dump: stat failed\n";
      WriteLog(msg, sizeof(msg) - 1);
      if (close_fd)
        IGNORE_RET(sys_close(fd));
      return;
    }
  
    *file_data = reinterpret_cast<uint8_t*>(allocator->Alloc(st.st_size));
    if (!(*file_data)) {
      static const char msg[] = "Cannot upload crash dump: cannot alloc\n";
      WriteLog(msg, sizeof(msg) - 1);
      if (close_fd)
        IGNORE_RET(sys_close(fd));
      return;
    }
    my_memset(*file_data, 0xf, st.st_size);
  
    *size = st.st_size;
    int byte_read = sys_read(fd, *file_data, *size);
    if (byte_read == -1) {
      static const char msg[] = "Cannot upload crash dump: read failed\n";
      WriteLog(msg, sizeof(msg) - 1);
      if (close_fd)
        IGNORE_RET(sys_close(fd));
      return;
    }
  
    if (close_fd)
      IGNORE_RET(sys_close(fd));
  }
  
  void LoadDataFromFile(google_breakpad::PageAllocator* allocator,
                        const char* filename,
                        int* fd,
                        uint8_t** file_data,
                        size_t* size) {
    // WARNING: this code runs in a compromised context. It may not call into
    // libc nor allocate memory normally.
    *fd = sys_open(filename, O_RDONLY, 0);
    *size = 0;
  
    if (*fd < 0) {
      static const char msg[] = "Cannot upload crash dump: failed to open\n";
      WriteLog(msg, sizeof(msg) - 1);
      return;
    }
  
    LoadDataFromFD(allocator, *fd, true, file_data, size);
  }
  
  // Spawn the appropriate upload process for the current OS:
  // - generic Linux invokes wget.
  // - ChromeOS invokes crash_reporter.
  // |dumpfile| is the path to the dump data file.
  // |mime_boundary| is only used on Linux.
  // |exe_buf| is only used on CrOS and is the crashing process' name.
  void ExecUploadProcessOrTerminate(const BreakpadInfo& info,
                                    const char* dumpfile,
                                    const char* mime_boundary,
                                    const char* exe_buf,
                                    google_breakpad::PageAllocator* allocator) {
    // The --header argument to wget looks like:
    //   --header=Content-Type: multipart/form-data; boundary=XYZ
    // where the boundary has two fewer leading '-' chars
    static const char header_msg[] =
        "--header=Content-Type: multipart/form-data; boundary=";
    char* const header = reinterpret_cast<char*>(
        allocator->Alloc(sizeof(header_msg) - 1 + strlen(mime_boundary) - 2 + 1));
    memcpy(header, header_msg, sizeof(header_msg) - 1);
    memcpy(header + sizeof(header_msg) - 1, mime_boundary + 2,
           strlen(mime_boundary) - 2);
    // We grab the NUL byte from the end of |mime_boundary|.
  
    // The --post-file argument to wget looks like:
    //   --post-file=/tmp/...
    static const char post_file_msg[] = "--post-file=";
    char* const post_file = reinterpret_cast<char*>(
        allocator->Alloc(sizeof(post_file_msg) - 1 + strlen(dumpfile) + 1));
    memcpy(post_file, post_file_msg, sizeof(post_file_msg) - 1);
    memcpy(post_file + sizeof(post_file_msg) - 1, dumpfile, strlen(dumpfile));
  
    static const char kWgetBinary[] = "/usr/bin/wget";
    const char* args[] = {
        kWgetBinary,    header, post_file, info.upload_url,
        "--timeout=60",  // Set a timeout so we don't hang forever.
        "--tries=1",     // Don't retry if the upload fails.
        "--quiet",       // Be silent.
        "-O",            // output reply to /dev/null.
        "/dev/fd/3",    NULL,
    };
    static const char msg[] =
        "Cannot upload crash dump: cannot exec "
        "/usr/bin/wget\n";
    execve(args[0], const_cast<char**>(args), environ);
    WriteLog(msg, sizeof(msg) - 1);
    sys__exit(1);
  }
  
  // Runs in the helper process to wait for the upload process running
  // ExecUploadProcessOrTerminate() to finish. Returns the number of bytes written
  // to |fd| and save the written contents to |buf|.
  // |buf| needs to be big enough to hold |bytes_to_read| + 1 characters.
  size_t WaitForCrashReportUploadProcess(int fd,
                                         size_t bytes_to_read,
                                         char* buf) {
    size_t bytes_read = 0;
  
    // Upload should finish in about 10 seconds. Add a few more 500 ms
    // internals to account for process startup time.
    for (size_t wait_count = 0; wait_count < 24; ++wait_count) {
      struct kernel_pollfd poll_fd;
      poll_fd.fd = fd;
      poll_fd.events = POLLIN | POLLPRI | POLLERR;
      int ret = sys_poll(&poll_fd, 1, 500);
      if (ret < 0) {
        // Error
        break;
      } else if (ret > 0) {
        // There is data to read.
        ssize_t len = HANDLE_EINTR(
            sys_read(fd, buf + bytes_read, bytes_to_read - bytes_read));
        if (len < 0)
          break;
        bytes_read += len;
        if (bytes_read == bytes_to_read)
          break;
      }
      // |ret| == 0 -> timed out, continue waiting.
      // or |bytes_read| < |bytes_to_read| still, keep reading.
    }
    buf[bytes_to_read] = 0;  // Always NUL terminate the buffer.
    return bytes_read;
  }
  
  // |buf| should be |expected_len| + 1 characters in size and NULL terminated.
  bool IsValidCrashReportId(const char* buf,
                            size_t bytes_read,
                            size_t expected_len) {
    if (bytes_read != expected_len)
      return false;
    for (size_t i = 0; i < bytes_read; ++i) {
      if (!my_isxdigit(buf[i]) && buf[i] != '-')
        return false;
    }
    return true;
  }
  
  // |buf| should be |expected_len| + 1 characters in size and NULL terminated.
  void HandleCrashReportId(const char* buf,
                           size_t bytes_read,
                           size_t expected_len) {
    if (!IsValidCrashReportId(buf, bytes_read, expected_len)) {
      static const char msg[] = "Failed to get crash dump id.";
      WriteLog(msg, sizeof(msg) - 1);
      WriteNewline();
  
      static const char id_msg[] = "Report Id: ";
      WriteLog(id_msg, sizeof(id_msg) - 1);
      WriteLog(buf, bytes_read);
      WriteNewline();
      return;
    }
  
    // Write crash dump id to stderr.
    static const char msg[] = "Crash dump id: ";
    WriteLog(msg, sizeof(msg) - 1);
    WriteLog(buf, my_strlen(buf));
    WriteNewline();
  
    // Write crash dump id to crash log as: seconds_since_epoch,crash_id
    struct kernel_timeval tv;
    if (!sys_gettimeofday(&tv, NULL)) {
      uint64_t time = kernel_timeval_to_ms(&tv) / 1000;
      char time_str[kUint64StringSize];
      const unsigned time_len = my_uint64_len(time);
      my_uint64tos(time_str, time, time_len);
  
      const int kLogOpenFlags = O_CREAT | O_WRONLY | O_APPEND | O_CLOEXEC;
      int log_fd = sys_open(g_crash_log_path, kLogOpenFlags, 0600);
      if (log_fd > 0) {
        sys_write(log_fd, time_str, time_len);
        sys_write(log_fd, ",", 1);
        sys_write(log_fd, buf, my_strlen(buf));
        sys_write(log_fd, "\n", 1);
        IGNORE_RET(sys_close(log_fd));
      }
    }
  }
  
  }  // namespace
  
  char g_crash_log_path[256];
  
  void HandleCrashDump(const BreakpadInfo& info) {
    int dumpfd;
    bool keep_fd = false;
    size_t dump_size;
    uint8_t* dump_data;
    google_breakpad::PageAllocator allocator;
    const char* exe_buf = NULL;
  
    if (info.fd != -1) {
      // Dump is provided with an open FD.
      keep_fd = true;
      dumpfd = info.fd;
  
      // The FD is pointing to the end of the file.
      // Rewind, we'll read the data next.
      if (lseek(dumpfd, 0, SEEK_SET) == -1) {
        static const char msg[] =
            "Cannot upload crash dump: failed to "
            "reposition minidump FD\n";
        WriteLog(msg, sizeof(msg) - 1);
        IGNORE_RET(sys_close(dumpfd));
        return;
      }
      LoadDataFromFD(&allocator, info.fd, false, &dump_data, &dump_size);
    } else {
      // Dump is provided with a path.
      keep_fd = false;
      LoadDataFromFile(&allocator, info.filename, &dumpfd, &dump_data,
                       &dump_size);
    }
  
    // We need to build a MIME block for uploading to the server. Since we are
    // going to fork and run wget, it needs to be written to a temp file.
    const int ufd = sys_open("/dev/urandom", O_RDONLY, 0);
    if (ufd < 0) {
      static const char msg[] =
          "Cannot upload crash dump because /dev/urandom"
          " is missing\n";
      WriteLog(msg, sizeof(msg) - 1);
      return;
    }
  
    static const char temp_file_template[] =
        "/tmp/chromium-upload-XXXXXXXXXXXXXXXX";
    char temp_file[sizeof(temp_file_template)];
    int temp_file_fd = -1;
    if (keep_fd) {
      temp_file_fd = dumpfd;
      // Rewind the destination, we are going to overwrite it.
      if (lseek(dumpfd, 0, SEEK_SET) == -1) {
        static const char msg[] =
            "Cannot upload crash dump: failed to "
            "reposition minidump FD (2)\n";
        WriteLog(msg, sizeof(msg) - 1);
        IGNORE_RET(sys_close(dumpfd));
        return;
      }
    } else {
      if (info.upload) {
        memcpy(temp_file, temp_file_template, sizeof(temp_file_template));
  
        for (unsigned i = 0; i < 10; ++i) {
          uint64_t t;
          sys_read(ufd, &t, sizeof(t));
          write_uint64_hex(temp_file + sizeof(temp_file) - (16 + 1), t);
  
          temp_file_fd = sys_open(temp_file, O_WRONLY | O_CREAT | O_EXCL, 0600);
          if (temp_file_fd >= 0)
            break;
        }
  
        if (temp_file_fd < 0) {
          static const char msg[] =
              "Failed to create temporary file in /tmp: "
              "cannot upload crash dump\n";
          WriteLog(msg, sizeof(msg) - 1);
          IGNORE_RET(sys_close(ufd));
          return;
        }
      } else {
        temp_file_fd = sys_open(info.filename, O_WRONLY, 0600);
        if (temp_file_fd < 0) {
          static const char msg[] = "Failed to save crash dump: failed to open\n";
          WriteLog(msg, sizeof(msg) - 1);
          IGNORE_RET(sys_close(ufd));
          return;
        }
      }
    }
  
    // The MIME boundary is 28 hyphens, followed by a 64-bit nonce and a NUL.
    char mime_boundary[28 + 16 + 1];
    my_memset(mime_boundary, '-', 28);
    uint64_t boundary_rand;
    sys_read(ufd, &boundary_rand, sizeof(boundary_rand));
    write_uint64_hex(mime_boundary + 28, boundary_rand);
    mime_boundary[28 + 16] = 0;
    IGNORE_RET(sys_close(ufd));
  
    // The MIME block looks like this:
    //   BOUNDARY \r\n
    //   Content-Disposition: form-data; name="prod" \r\n \r\n
    //   Chrome_Linux \r\n
    //   BOUNDARY \r\n
    //   Content-Disposition: form-data; name="ver" \r\n \r\n
    //   1.2.3.4 \r\n
    //   BOUNDARY \r\n
    //
    //   zero or one:
    //   Content-Disposition: form-data; name="ptime" \r\n \r\n
    //   abcdef \r\n
    //   BOUNDARY \r\n
    //
    //   zero or one:
    //   Content-Disposition: form-data; name="ptype" \r\n \r\n
    //   abcdef \r\n
    //   BOUNDARY \r\n
    //
    //   zero or one:
    //   Content-Disposition: form-data; name="lsb-release" \r\n \r\n
    //   abcdef \r\n
    //   BOUNDARY \r\n
    //
    //   zero or one:
    //   Content-Disposition: form-data; name="oom-size" \r\n \r\n
    //   1234567890 \r\n
    //   BOUNDARY \r\n
    //
    //   zero or more (up to CrashKeyStorage::num_entries = 64):
    //   Content-Disposition: form-data; name=crash-key-name \r\n
    //   crash-key-value \r\n
    //   BOUNDARY \r\n
    //
    //   Content-Disposition: form-data; name="dump"; filename="dump" \r\n
    //   Content-Type: application/octet-stream \r\n \r\n
    //   <dump contents>
    //   \r\n BOUNDARY -- \r\n
  
    MimeWriter writer(temp_file_fd, mime_boundary);
    {
      writer.AddBoundary();
      if (info.pid > 0) {
        char pid_value_buf[kUint64StringSize];
        uint64_t pid_value_len = my_uint64_len(info.pid);
        my_uint64tos(pid_value_buf, info.pid, pid_value_len);
        static const char pid_key_name[] = "pid";
        writer.AddPairData(pid_key_name, sizeof(pid_key_name) - 1, pid_value_buf,
                           pid_value_len);
        writer.AddBoundary();
      }
      writer.Flush();
    }
  
    if (info.process_start_time > 0) {
      struct kernel_timeval tv;
      if (!sys_gettimeofday(&tv, NULL)) {
        uint64_t time = kernel_timeval_to_ms(&tv);
        if (time > info.process_start_time) {
          time -= info.process_start_time;
          char time_str[kUint64StringSize];
          const unsigned time_len = my_uint64_len(time);
          my_uint64tos(time_str, time, time_len);
  
          static const char process_time_msg[] = "ptime";
          writer.AddPairData(process_time_msg, sizeof(process_time_msg) - 1,
                             time_str, time_len);
          writer.AddBoundary();
          writer.Flush();
        }
      }
    }
  
    if (info.distro_length) {
      static const char distro_msg[] = "lsb-release";
      writer.AddPairString(distro_msg, info.distro);
      writer.AddBoundary();
      writer.Flush();
    }
  
    if (info.oom_size) {
      char oom_size_str[kUint64StringSize];
      const unsigned oom_size_len = my_uint64_len(info.oom_size);
      my_uint64tos(oom_size_str, info.oom_size, oom_size_len);
      static const char oom_size_msg[] = "oom-size";
      writer.AddPairData(oom_size_msg, sizeof(oom_size_msg) - 1, oom_size_str,
                         oom_size_len);
      writer.AddBoundary();
      writer.Flush();
    }
  
    if (info.crash_keys) {
      CrashKeyStorage::Iterator crash_key_iterator(*info.crash_keys);
      const CrashKeyStorage::Entry* entry;
      while ((entry = crash_key_iterator.Next())) {
        writer.AddPairString(entry->key, entry->value);
        writer.AddBoundary();
        writer.Flush();
      }
    }
  
    writer.AddFileContents(g_dump_msg, dump_data, dump_size);
    writer.AddEnd();
    writer.Flush();
  
    IGNORE_RET(sys_close(temp_file_fd));
  
    if (!info.upload)
      return;
  
    const pid_t child = sys_fork();
    if (!child) {
      // Spawned helper process.
      //
      // This code is called both when a browser is crashing (in which case,
      // nothing really matters any more) and when a renderer/plugin crashes, in
      // which case we need to continue.
      //
      // Since we are a multithreaded app, if we were just to fork(), we might
      // grab file descriptors which have just been created in another thread and
      // hold them open for too long.
      //
      // Thus, we have to loop and try and close everything.
      const int fd = sys_open("/proc/self/fd", O_DIRECTORY | O_RDONLY, 0);
      if (fd < 0) {
        for (unsigned i = 3; i < 8192; ++i)
          IGNORE_RET(sys_close(i));
      } else {
        google_breakpad::DirectoryReader reader(fd);
        const char* name;
        while (reader.GetNextEntry(&name)) {
          int i;
          if (my_strtoui(&i, name) && i > 2 && i != fd)
            IGNORE_RET(sys_close(i));
          reader.PopEntry();
        }
  
        IGNORE_RET(sys_close(fd));
      }
  
      IGNORE_RET(sys_setsid());
  
      // Leave one end of a pipe in the upload process and watch for it getting
      // closed by the upload process exiting.
      int fds[2];
      if (sys_pipe(fds) >= 0) {
        const pid_t upload_child = sys_fork();
        if (!upload_child) {
          // Upload process.
          IGNORE_RET(sys_close(fds[0]));
          IGNORE_RET(sys_dup2(fds[1], 3));
          ExecUploadProcessOrTerminate(info, temp_file, mime_boundary, exe_buf,
                                       &allocator);
        }
  
        // Helper process.
        if (upload_child > 0) {
          IGNORE_RET(sys_close(fds[1]));
  
          const size_t kCrashIdLength = 36;
          char id_buf[kCrashIdLength + 1];
          size_t bytes_read =
              WaitForCrashReportUploadProcess(fds[0], kCrashIdLength, id_buf);
          HandleCrashReportId(id_buf, bytes_read, kCrashIdLength);
  
          if (sys_waitpid(upload_child, NULL, WNOHANG) == 0) {
            // Upload process is still around, kill it.
            sys_kill(upload_child, SIGKILL);
          }
        }
      }
  
      // Helper process.
      IGNORE_RET(sys_unlink(info.filename));
      IGNORE_RET(sys_unlink(temp_file));
      sys__exit(0);
    }
  
    // Main browser process.
    if (child <= 0)
      return;
    (void)HANDLE_EINTR(sys_waitpid(child, NULL, 0));
  }
  
  size_t WriteLog(const char* buf, size_t nbytes) {
    return sys_write(2, buf, nbytes);
  }
  
  size_t WriteNewline() {
    return WriteLog("\n", 1);
  }
  
  }  // namespace crash_reporter
  