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

#include "mojo/public/cpp/base/read_only_file_mojom_traits.h"

#include "base/files/file.h"
#include "build/build_config.h"

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#if defined(OS_WIN)
#include <windows.h>
#include <winternl.h>
#endif

namespace mojo {
namespace {
#if defined(OS_WIN)
bool GetGrantedAccess(HANDLE handle, DWORD* flags) {
  static const auto nt_query_object =
      reinterpret_cast<decltype(&NtQueryObject)>(
          GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryObject"));
  PUBLIC_OBJECT_BASIC_INFORMATION info;
  ULONG len = sizeof(info);
  ULONG consumed = 0;
  auto ret =
      nt_query_object(handle, ObjectBasicInformation, &info, len, &consumed);
  if (ret)
    return false;
  *flags = info.GrantedAccess;
  return true;
}
#endif  // defined(OS_WIN)

// True if the underlying handle is only readable. Where possible this excludes
// deletion, writing, truncation, append and other operations that might modify
// the underlying file. False if we can tell that the file could be modified.
// On platforms where we cannot test the handle, always returns true.
bool IsReadOnlyFile(base::File& file) {
  bool is_readonly = true;
#if defined(OS_WIN)
  DWORD flags = 0;
  if (!GetGrantedAccess(file.GetPlatformFile(), &flags))
    return false;

  // Cannot use GENERIC_WRITE as that includes SYNCHRONIZE.
  // This is ~(all the writable permissions).
  is_readonly =
      !(flags & (FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA |
                 FILE_WRITE_EA | WRITE_DAC | WRITE_OWNER | DELETE));
#elif defined(OS_FUCHSIA) || \
    (defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_AIX))
  is_readonly =
      (fcntl(file.GetPlatformFile(), F_GETFL) & O_ACCMODE) == O_RDONLY;
#endif
  return is_readonly;
}

bool IsPhysicalFile(base::File& file) {
#if defined(OS_WIN)
  // Verify if this is a real file (not a socket/pipe etc.).
  DWORD type = GetFileType(file.GetPlatformFile());
  return type == FILE_TYPE_DISK;
#else
  // This may block but in practice this is unlikely for already opened
  // physical files.
  struct stat st;
  if (fstat(file.GetPlatformFile(), &st) != 0)
    return false;
  return S_ISREG(st.st_mode);
#endif
}

}  // namespace

mojo::PlatformHandle StructTraits<mojo_base::mojom::ReadOnlyFileDataView,
                                  base::File>::fd(base::File& file) {
  DCHECK(file.IsValid());
  // For now we require real files as on some platforms it is too difficult to
  // be sure that more general handles cannot be written or made writable. This
  // could be relaxed if an interface needs readonly pipes. This check may block
  // so cannot be enabled in release builds.
  DCHECK(IsPhysicalFile(file));
  CHECK(IsReadOnlyFile(file));

  return mojo::PlatformHandle(
      base::ScopedPlatformFile(file.TakePlatformFile()));
}

bool StructTraits<mojo_base::mojom::ReadOnlyFileDataView, base::File>::Read(
    mojo_base::mojom::ReadOnlyFileDataView data,
    base::File* file) {
  *file = base::File(data.TakeFd().TakePlatformFile(), data.async());
  return true;
}

}  // namespace mojo
