// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <sys/mman.h>
#include <unistd.h>

#include <functional>
#include <string>
#include <utility>
#include <vector>

#include "test/util/file_descriptor.h"
#include "test/util/memory_util.h"
#include "test/util/posix_error.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"

namespace gvisor {
namespace testing {

namespace {

// Parameters for msync tests. Use a std::tuple so we can use
// ::testing::Combine.
using MsyncTestParam =
    std::tuple<int,                                    // msync flags
               std::function<PosixErrorOr<Mapping>()>  // returns mapping to
                                                       // msync
               >;

class MsyncParameterizedTest : public ::testing::TestWithParam<MsyncTestParam> {
 protected:
  int msync_flags() const { return std::get<0>(GetParam()); }

  PosixErrorOr<Mapping> GetMapping() const { return std::get<1>(GetParam())(); }
};

// All valid msync(2) flag combinations, not including MS_INVALIDATE. ("Linux
// permits a call to msync() that specifies neither [MS_SYNC or MS_ASYNC], with
// semantics that are (currently) equivalent to specifying MS_ASYNC." -
// msync(2))
constexpr std::initializer_list<int> kMsyncFlags = {MS_SYNC, MS_ASYNC, 0};

// Returns functions that return mappings that should be successfully
// msync()able.
std::vector<std::function<PosixErrorOr<Mapping>()>> SyncableMappings() {
  std::vector<std::function<PosixErrorOr<Mapping>()>> funcs;
  for (bool const writable : {false, true}) {
    for (int const mflags : {MAP_PRIVATE, MAP_SHARED}) {
      int const prot = PROT_READ | (writable ? PROT_WRITE : 0);
      int const oflags = O_CREAT | (writable ? O_RDWR : O_RDONLY);
      funcs.push_back([=] { return MmapAnon(kPageSize, prot, mflags); });
      funcs.push_back([=]() -> PosixErrorOr<Mapping> {
        std::string const path = NewTempAbsPath();
        ASSIGN_OR_RETURN_ERRNO(auto fd, Open(path, oflags, 0644));
        // Don't unlink the file since that breaks save/restore. Just let the
        // test infrastructure clean up all of our temporary files when we're
        // done.
        return Mmap(nullptr, kPageSize, prot, mflags, fd.get(), 0);
      });
    }
  }
  return funcs;
}

PosixErrorOr<Mapping> NoMappings() {
  return PosixError(EINVAL, "unexpected attempt to create a mapping");
}

// "Fixture" for msync tests that hold for all valid flags, but do not create
// mappings.
using MsyncNoMappingTest = MsyncParameterizedTest;

TEST_P(MsyncNoMappingTest, UnmappedAddressWithZeroLengthSucceeds) {
  EXPECT_THAT(msync(nullptr, 0, msync_flags()), SyscallSucceeds());
}

TEST_P(MsyncNoMappingTest, UnmappedAddressWithNonzeroLengthFails) {
  EXPECT_THAT(msync(nullptr, kPageSize, msync_flags()),
              SyscallFailsWithErrno(ENOMEM));
}

INSTANTIATE_TEST_SUITE_P(All, MsyncNoMappingTest,
                         ::testing::Combine(::testing::ValuesIn(kMsyncFlags),
                                            ::testing::Values(NoMappings)));

// "Fixture" for msync tests that are not parameterized by msync flags, but do
// create mappings.
using MsyncNoFlagsTest = MsyncParameterizedTest;

TEST_P(MsyncNoFlagsTest, BothSyncAndAsyncFails) {
  auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping());
  EXPECT_THAT(msync(m.ptr(), m.len(), MS_SYNC | MS_ASYNC),
              SyscallFailsWithErrno(EINVAL));
}

INSTANTIATE_TEST_SUITE_P(
    All, MsyncNoFlagsTest,
    ::testing::Combine(::testing::Values(0),  // ignored
                       ::testing::ValuesIn(SyncableMappings())));

// "Fixture" for msync tests parameterized by both msync flags and sources of
// mappings.
using MsyncFullParamTest = MsyncParameterizedTest;

TEST_P(MsyncFullParamTest, NormallySucceeds) {
  auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping());
  EXPECT_THAT(msync(m.ptr(), m.len(), msync_flags()), SyscallSucceeds());
}

TEST_P(MsyncFullParamTest, UnalignedLengthSucceeds) {
  auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping());
  EXPECT_THAT(msync(m.ptr(), m.len() - 1, msync_flags()), SyscallSucceeds());
}

TEST_P(MsyncFullParamTest, UnalignedAddressFails) {
  auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping());
  EXPECT_THAT(
      msync(reinterpret_cast<void*>(m.addr() + 1), m.len() - 1, msync_flags()),
      SyscallFailsWithErrno(EINVAL));
}

TEST_P(MsyncFullParamTest, InvalidateUnlockedSucceeds) {
  auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping());
  EXPECT_THAT(msync(m.ptr(), m.len(), msync_flags() | MS_INVALIDATE),
              SyscallSucceeds());
}

// The test for MS_INVALIDATE on mlocked pages is in mlock.cc since it requires
// probing for mlock support.

INSTANTIATE_TEST_SUITE_P(
    All, MsyncFullParamTest,
    ::testing::Combine(::testing::ValuesIn(kMsyncFlags),
                       ::testing::ValuesIn(SyncableMappings())));

}  // namespace

}  // namespace testing
}  // namespace gvisor
