// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/system/sys_info.h"

#include <fidl/fuchsia.buildinfo/cpp/fidl.h>
#include <fidl/fuchsia.hwinfo/cpp/fidl.h>
#include <sys/statvfs.h>
#include <zircon/syscalls.h>

#include <string>

#include "base/containers/flat_map.h"
#include "base/files/file_util.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/system_info.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/numerics/clamped_math.h"
#include "base/strings/string_piece.h"
#include "base/synchronization/lock.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"

namespace base {

namespace {

bool GetDiskSpaceInfo(const FilePath& path,
                      int64_t* available_bytes,
                      int64_t* total_bytes) {
  struct statvfs stats;
  if (statvfs(path.value().c_str(), &stats) != 0) {
    PLOG(ERROR) << "statvfs() for path:" << path;
    return false;
  }

  if (available_bytes) {
    ClampedNumeric<int64_t> available_blocks(stats.f_bavail);
    *available_bytes = available_blocks * stats.f_frsize;
  }

  if (total_bytes) {
    ClampedNumeric<int64_t> total_blocks(stats.f_blocks);
    *total_bytes = total_blocks * stats.f_frsize;
  }

  return true;
}

struct TotalDiskSpace {
  Lock lock;
  flat_map<FilePath, int64_t> space_map GUARDED_BY(lock);
};

TotalDiskSpace& GetTotalDiskSpace() {
  static NoDestructor<TotalDiskSpace> total_disk_space;
  return *total_disk_space;
}

// Returns the total-disk-space set for the volume containing |path|. If
// |volume_path| is non-null then it receives the path to the relevant volume.
// Returns -1, and does not modify |volume_path|, if no match is found. Also
// returns -1 if |path| is not absolute.
int64_t GetAmountOfTotalDiskSpaceAndVolumePath(const FilePath& path,
                                               FilePath* volume_path) {
  if (!path.IsAbsolute()) {
    return -1;
  }
  TotalDiskSpace& total_disk_space = GetTotalDiskSpace();

  AutoLock l(total_disk_space.lock);
  int64_t result = -1;
  FilePath matched_path;
  for (const auto& path_and_size : total_disk_space.space_map) {
    if (path_and_size.first == path || path_and_size.first.IsParent(path)) {
      // If a deeper path was already matched then ignore this entry.
      if (!matched_path.empty() && !matched_path.IsParent(path_and_size.first))
        continue;
      matched_path = path_and_size.first;
      result = path_and_size.second;
    }
  }

  if (volume_path)
    *volume_path = matched_path;
  return result;
}

}  // namespace

// static
uint64_t SysInfo::AmountOfPhysicalMemoryImpl() {
  return zx_system_get_physmem();
}

// static
uint64_t SysInfo::AmountOfAvailablePhysicalMemoryImpl() {
  // TODO(crbug.com/986608): Implement this when Fuchsia supports it.
  NOTIMPLEMENTED_LOG_ONCE();
  return 0;
}

// static
int SysInfo::NumberOfProcessors() {
  return static_cast<int>(zx_system_get_num_cpus());
}

// static
uint64_t SysInfo::AmountOfVirtualMemory() {
  // Fuchsia does not provide this type of information.
  // Return zero to indicate that there is unlimited available virtual memory.
  return 0;
}

// static
std::string SysInfo::OperatingSystemName() {
  return "Fuchsia";
}

// static
int64_t SysInfo::AmountOfFreeDiskSpace(const FilePath& path) {
  ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);

  // First check whether there is a soft-quota that applies to |path|.
  FilePath volume_path;
  const int64_t total_space =
      GetAmountOfTotalDiskSpaceAndVolumePath(path, &volume_path);
  if (total_space >= 0) {
    // TODO(crbug.com/1148334): Replace this with an efficient implementation.
    const int64_t used_space = ComputeDirectorySize(volume_path);
    return std::max(0l, total_space - used_space);
  }

  // Report the actual amount of free space in |path|'s filesystem.
  int64_t available;
  if (GetDiskSpaceInfo(path, &available, nullptr))
    return available;

  return -1;
}

// static
int64_t SysInfo::AmountOfTotalDiskSpace(const FilePath& path) {
  ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);

  if (path.empty())
    return -1;

  // Return the soft-quota that applies to |path|, if one is configured.
  int64_t total_space = GetAmountOfTotalDiskSpaceAndVolumePath(path, nullptr);
  if (total_space >= 0)
    return total_space;

  // Report the actual space in |path|'s filesystem.
  if (GetDiskSpaceInfo(path, nullptr, &total_space))
    return total_space;

  return -1;
}

// static
void SysInfo::SetAmountOfTotalDiskSpace(const FilePath& path, int64_t bytes) {
  DCHECK(path.IsAbsolute());
  TotalDiskSpace& total_disk_space = GetTotalDiskSpace();
  AutoLock l(total_disk_space.lock);
  if (bytes >= 0)
    total_disk_space.space_map[path] = bytes;
  else
    total_disk_space.space_map.erase(path);
}

// static
std::string SysInfo::OperatingSystemVersion() {
  const auto& build_info = GetCachedBuildInfo();
  return build_info.version().value_or("");
}

// static
void SysInfo::OperatingSystemVersionNumbers(int32_t* major_version,
                                            int32_t* minor_version,
                                            int32_t* bugfix_version) {
  // TODO(crbug.com/1348711): Implement this when Fuchsia supports it.
  NOTIMPLEMENTED_LOG_ONCE();
  *major_version = 0;
  *minor_version = 0;
  *bugfix_version = 0;
}

// static
std::string SysInfo::OperatingSystemArchitecture() {
#if defined(ARCH_CPU_X86_64)
  return "x86_64";
#elif defined(ARCH_CPU_ARM64)
  return "aarch64";
#else
#error Unsupported architecture.
#endif
}

// static
std::string SysInfo::CPUModelName() {
  // TODO(crbug.com/1233859): Implement this when Fuchsia supports it.
  NOTIMPLEMENTED_LOG_ONCE();
  return std::string();
}

// static
size_t SysInfo::VMAllocationGranularity() {
  return static_cast<size_t>(getpagesize());
}

// static
int SysInfo::NumberOfEfficientProcessorsImpl() {
  NOTIMPLEMENTED();
  return 0;
}

SysInfo::HardwareInfo SysInfo::GetHardwareInfoSync() {
  const auto product_info = GetProductInfo();

  return {
      .manufacturer = product_info.manufacturer().value_or(""),
      .model = product_info.model().value_or(""),
  };
}

}  // namespace base
