text: /*
   * libjingle
   * Copyright 2008 Google Inc.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   *  1. Redistributions of source code must retain the above copyright notice,
   *     this list of conditions and the following disclaimer.
   *  2. Redistributions in binary form must reproduce the above copyright notice,
   *     this list of conditions and the following disclaimer in the documentation
   *     and/or other materials provided with the distribution.
   *  3. The name of the author may not be used to endorse or promote products
   *     derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
   * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
   * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
   * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  #if defined(LINUX) || defined(ANDROID)
  #include "talk/base/linux.h"
  
  #include <ctype.h>
  
  #include <errno.h>
  #include <sys/utsname.h>
  #include <sys/wait.h>
  
  #include <cstdio>
  #include <set>
  
  #include "talk/base/stringencode.h"
  
  namespace talk_base {
  
  static const char kCpuInfoFile[] = "/proc/cpuinfo";
  static const char kCpuMaxFreqFile[] =
      "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq";
  
  ProcCpuInfo::ProcCpuInfo() {
  }
  
  ProcCpuInfo::~ProcCpuInfo() {
  }
  
  bool ProcCpuInfo::LoadFromSystem() {
    ConfigParser procfs;
    if (!procfs.Open(kCpuInfoFile)) {
      return false;
    }
    return procfs.Parse(&sections_);
  };
  
  bool ProcCpuInfo::GetSectionCount(size_t* count) {
    if (sections_.empty()) {
      return false;
    }
    if (count) {
      *count = sections_.size();
    }
    return true;
  }
  
  bool ProcCpuInfo::GetNumCpus(int* num) {
    if (sections_.empty()) {
      return false;
    }
    int total_cpus = 0;
  #if defined(__arm__)
    // Count the number of blocks that have a "processor" key defined. On ARM,
    // there may be extra blocks of information that aren't per-processor.
    size_t section_count = sections_.size();
    for (size_t i = 0; i < section_count; ++i) {
      int processor_id;
      if (GetSectionIntValue(i, "processor", &processor_id)) {
        ++total_cpus;
      }
    }
    // Single core ARM systems don't include "processor" keys at all, so return
    // that we have a single core if we didn't find any explicitly above.
    if (total_cpus == 0) {
      total_cpus = 1;
    }
  #else
    // On X86, there is exactly one info section per processor.
    total_cpus = static_cast<int>(sections_.size());
  #endif
    if (num) {
      *num = total_cpus;
    }
    return true;
  }
  
  bool ProcCpuInfo::GetNumPhysicalCpus(int* num) {
    if (sections_.empty()) {
      return false;
    }
    // TODO: /proc/cpuinfo only reports cores that are currently
    // _online_, so this may underreport the number of physical cores.
  #if defined(__arm__)
    // ARM (currently) has no hyperthreading, so just return the same value
    // as GetNumCpus.
    return GetNumCpus(num);
  #else
    int total_cores = 0;
    std::set<int> physical_ids;
    size_t section_count = sections_.size();
    for (size_t i = 0; i < section_count; ++i) {
      int physical_id;
      int cores;
      // Count the cores for the physical id only if we have not counted the id.
      if (GetSectionIntValue(i, "physical id", &physical_id) &&
          GetSectionIntValue(i, "cpu cores", &cores) &&
          physical_ids.find(physical_id) == physical_ids.end()) {
        physical_ids.insert(physical_id);
        total_cores += cores;
      }
    }
  
    if (num) {
      *num = total_cores;
    }
    return true;
  #endif
  }
  
  bool ProcCpuInfo::GetCpuFamily(int* id) {
    int cpu_family = 0;
  
    GetSectionIntValue(0, "cpu family", &cpu_family);
  
    if (id) {
      *id = cpu_family;
    }
    return true;
  }
  
  bool ProcCpuInfo::GetSectionStringValue(size_t section_num,
                                          const std::string& key,
                                          std::string* result) {
    if (section_num >= sections_.size()) {
      return false;
    }
    ConfigParser::SimpleMap::iterator iter = sections_[section_num].find(key);
    if (iter == sections_[section_num].end()) {
      return false;
    }
    *result = iter->second;
    return true;
  }
  
  bool ProcCpuInfo::GetSectionIntValue(size_t section_num,
                                       const std::string& key,
                                       int* result) {
    if (section_num >= sections_.size()) {
      return false;
    }
    ConfigParser::SimpleMap::iterator iter = sections_[section_num].find(key);
    if (iter == sections_[section_num].end()) {
      return false;
    }
    return FromString(iter->second, result);
  }
  
  ConfigParser::ConfigParser() {}
  
  ConfigParser::~ConfigParser() {}
  
  bool ConfigParser::Open(const std::string& filename) {
    FileStream* fs = new FileStream();
    if (!fs->Open(filename, "r", NULL)) {
      return false;
    }
    instream_.reset(fs);
    return true;
  }
  
  void ConfigParser::Attach(StreamInterface* stream) {
    instream_.reset(stream);
  }
  
  bool ConfigParser::Parse(MapVector* key_val_pairs) {
    // Parses the file and places the found key-value pairs into key_val_pairs.
    SimpleMap section;
    while (ParseSection(&section)) {
      key_val_pairs->push_back(section);
      section.clear();
    }
    return (!key_val_pairs->empty());
  }
  
  bool ConfigParser::ParseSection(SimpleMap* key_val_pair) {
    // Parses the next section in the filestream and places the found key-value
    // pairs into key_val_pair.
    std::string key, value;
    while (ParseLine(&key, &value)) {
      (*key_val_pair)[key] = value;
    }
    return (!key_val_pair->empty());
  }
  
  bool ConfigParser::ParseLine(std::string* key, std::string* value) {
    // Parses the next line in the filestream and places the found key-value
    // pair into key and val.
    std::string line;
    if ((instream_->ReadLine(&line)) == EOF) {
      return false;
    }
    std::vector<std::string> tokens;
    if (2 != split(line, ':', &tokens)) {
      return false;
    }
    // Removes whitespace at the end of Key name
    size_t pos = tokens[0].length() - 1;
    while ((pos > 0) && isspace(tokens[0][pos])) {
      pos--;
    }
    tokens[0].erase(pos + 1);
    // Removes whitespace at the start of value
    pos = 0;
    while (pos < tokens[1].length() && isspace(tokens[1][pos])) {
      pos++;
    }
    tokens[1].erase(0, pos);
    *key = tokens[0];
    *value = tokens[1];
    return true;
  }
  
  static bool ExpectLineFromStream(FileStream* stream,
                                   std::string* out) {
    StreamResult res = stream->ReadLine(out);
    if (res != SR_SUCCESS) {
      if (res != SR_EOS) {
        LOG(LS_ERROR) << "Error when reading from stream";
      } else {
        LOG(LS_ERROR) << "Incorrect number of lines in stream";
      }
      return false;
    }
    return true;
  }
  
  static void ExpectEofFromStream(FileStream* stream) {
    std::string unused;
    StreamResult res = stream->ReadLine(&unused);
    if (res == SR_SUCCESS) {
      LOG(LS_WARNING) << "Ignoring unexpected extra lines from stream";
    } else if (res != SR_EOS) {
      LOG(LS_WARNING) << "Error when checking for extra lines from stream";
    }
  }
  
  // For caching the lsb_release output (reading it invokes a sub-process and
  // hence is somewhat expensive).
  static std::string lsb_release_string;
  static CriticalSection lsb_release_string_critsec;
  
  std::string ReadLinuxLsbRelease() {
    CritScope cs(&lsb_release_string_critsec);
    if (!lsb_release_string.empty()) {
      // Have cached result from previous call.
      return lsb_release_string;
    }
    // No cached result. Run lsb_release and parse output.
    POpenStream lsb_release_output;
    if (!lsb_release_output.Open("lsb_release -idrcs", "r", NULL)) {
      LOG_ERR(LS_ERROR) << "Can't run lsb_release";
      return lsb_release_string;  // empty
    }
    // Read in the command's output and build the string.
    std::ostringstream sstr;
    std::string line;
    int wait_status;
  
    if (!ExpectLineFromStream(&lsb_release_output, &line)) {
      return lsb_release_string;  // empty
    }
    sstr << "DISTRIB_ID=" << line;
  
    if (!ExpectLineFromStream(&lsb_release_output, &line)) {
      return lsb_release_string;  // empty
    }
    sstr << " DISTRIB_DESCRIPTION=\"" << line << '"';
  
    if (!ExpectLineFromStream(&lsb_release_output, &line)) {
      return lsb_release_string;  // empty
    }
    sstr << " DISTRIB_RELEASE=" << line;
  
    if (!ExpectLineFromStream(&lsb_release_output, &line)) {
      return lsb_release_string;  // empty
    }
    sstr << " DISTRIB_CODENAME=" << line;
  
    // Should not be anything left.
    ExpectEofFromStream(&lsb_release_output);
  
    lsb_release_output.Close();
    wait_status = lsb_release_output.GetWaitStatus();
    if (wait_status == -1 ||
        !WIFEXITED(wait_status) ||
        WEXITSTATUS(wait_status) != 0) {
      LOG(LS_WARNING) << "Unexpected exit status from lsb_release";
    }
  
    lsb_release_string = sstr.str();
  
    return lsb_release_string;
  }
  
  std::string ReadLinuxUname() {
    struct utsname buf;
    if (uname(&buf) < 0) {
      LOG_ERR(LS_ERROR) << "Can't call uname()";
      return std::string();
    }
    std::ostringstream sstr;
    sstr << buf.sysname << " "
         << buf.release << " "
         << buf.version << " "
         << buf.machine;
    return sstr.str();
  }
  
  int ReadCpuMaxFreq() {
    FileStream fs;
    std::string str;
    int freq = -1;
    if (!fs.Open(kCpuMaxFreqFile, "r", NULL) ||
        SR_SUCCESS != fs.ReadLine(&str) ||
        !FromString(str, &freq)) {
      return -1;
    }
    return freq;
  }
  
  }  // namespace talk_base
  
  #endif  // defined(LINUX) || defined(ANDROID)
  