/*
 * Copyright (c) 2024, Red Hat, Inc. and/or its affiliates.
 * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

#include "procMapsParser.hpp"
#include "runtime/os.hpp"
#include "utilities/globalDefinitions.hpp"

static bool is_lowercase_hex(char c) {
  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
}

static size_t max_mapping_line_len() {
  return 100 + // everything but the file name
         os::vm_page_size() // the file name (kernel limits /proc/pid/cmdline to one page
         ;
}

ProcSmapsParser::ProcSmapsParser(FILE* f) :
  _f(f), _linelen(max_mapping_line_len()), _line(nullptr) {
  assert(_f != nullptr, "Invalid file handle given");
  _line = NEW_C_HEAP_ARRAY(char, max_mapping_line_len(), mtInternal);
  _line[0] = '\0';
}

ProcSmapsParser::~ProcSmapsParser() {
  FREE_C_HEAP_ARRAY(char, _line);
}

bool ProcSmapsParser::read_line() {
  _line[0] = '\0';

  if (::fgets(_line, _linelen, _f) == nullptr) {
    // On error or EOF, ensure deterministic empty buffer
    _line[0] = '\0';
    return false;
  } else {
    return true;
  }
}

bool ProcSmapsParser::is_header_line() {
  // e.g. ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
  return is_lowercase_hex(_line[0]); // All non-header lines in /proc/pid/smaps start with upper-case letters.
}

void ProcSmapsParser::scan_header_line(ProcSmapsInfo& out) {
  const int items_read = ::sscanf(_line, "%p-%p %20s %*s %*s %*s %1024s",
                                  &out.from, &out.to, out.prot, out.filename);
  assert(items_read >= 2, "Expected header_line");
}

void ProcSmapsParser::scan_additional_line(ProcSmapsInfo& out) {
#define SCAN(key, var) \
 if (::sscanf(_line, key ": %zu kB", &var) == 1) { \
     var *= K; \
     return; \
 }
  SCAN("KernelPageSize", out.kernelpagesize);
  SCAN("Rss", out.rss);
  SCAN("AnonHugePages", out.anonhugepages);
  SCAN("Private_Hugetlb", out.private_hugetlb);
  SCAN("Shared_Hugetlb", out.shared_hugetlb);
  SCAN("Swap", out.swap);
#undef SCAN

  // scan THPeligible into a bool
  int thpel = 0;
  if (::sscanf(_line, "THPeligible: %d", &thpel) == 1) {
    assert(thpel == 1 || thpel == 0, "Unexpected value %d", thpel);
    out.thpeligible = (thpel == 1);
    return;
  }

  // scan some flags too
  if (strncmp(_line, "VmFlags:", 8) == 0) {
#define SCAN(flag) { out.flag = (::strstr(_line + 8, " " #flag) != nullptr); }
    SCAN(rd);
    SCAN(wr);
    SCAN(ex);
    SCAN(nr);
    SCAN(sh);
    SCAN(hg);
    SCAN(ht);
    SCAN(nh);
#undef SCAN
  }
}

bool ProcSmapsParser::parse_next(ProcSmapsInfo& out) {

  // Information about a single mapping reaches across several lines.
  out.reset();

  // Read header line, unless we already read it
  if (_line[0] == '\0') {
    if (!read_line()) {
      return false;
    }
  }
  assert(is_header_line(), "Not a header line: \"%s\".", _line);
  scan_header_line(out);

  while (true) {
    bool ok = read_line();
    if (!ok || is_header_line()) {
      break;  // EOF or next header
    }
    scan_additional_line(out);
  }

  return true;  // always return true if a mapping was parsed
}
