gvisor/test/syscalls/linux/priority.cc

217 lines
7.0 KiB
C++

// Copyright 2018 Google LLC
//
// 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/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_split.h"
#include "test/util/capability_util.h"
#include "test/util/fs_util.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
namespace gvisor {
namespace testing {
namespace {
// These tests are for both the getpriority(2) and setpriority(2) syscalls
// These tests are very rudimentary because getpriority and setpriority
// have not yet been fully implemented.
// Getpriority does something
TEST(GetpriorityTest, Implemented) {
// "getpriority() can legitimately return the value -1, it is necessary to
// clear the external variable errno prior to the call"
errno = 0;
EXPECT_THAT(getpriority(PRIO_PROCESS, /*who=*/0), SyscallSucceeds());
}
// Invalid which
TEST(GetpriorityTest, InvalidWhich) {
errno = 0;
EXPECT_THAT(getpriority(/*which=*/3, /*who=*/0),
SyscallFailsWithErrno(EINVAL));
}
// Process is found when which=PRIO_PROCESS
TEST(GetpriorityTest, ValidWho) {
errno = 0;
EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()), SyscallSucceeds());
}
// Process is not found when which=PRIO_PROCESS
TEST(GetpriorityTest, InvalidWho) {
errno = 0;
// Flaky, but it's tough to avoid a race condition when finding an unused pid
EXPECT_THAT(getpriority(PRIO_PROCESS, /*who=*/INT_MAX - 1),
SyscallFailsWithErrno(ESRCH));
}
// Setpriority does something
TEST(SetpriorityTest, Implemented) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
// No need to clear errno for setpriority():
// "The setpriority() call returns 0 if there is no error, or -1 if there is"
EXPECT_THAT(setpriority(PRIO_PROCESS, /*who=*/0, /*nice=*/16),
SyscallSucceeds());
}
// Invalid which
TEST(Setpriority, InvalidWhich) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
EXPECT_THAT(setpriority(/*which=*/3, /*who=*/0, /*nice=*/16),
SyscallFailsWithErrno(EINVAL));
}
// Process is found when which=PRIO_PROCESS
TEST(SetpriorityTest, ValidWho) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/16),
SyscallSucceeds());
}
// niceval is within the range [-20, 19]
TEST(SetpriorityTest, InsideRange) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
// Set 0 < niceval < 19
int nice = 12;
EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), nice), SyscallSucceeds());
errno = 0;
EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
SyscallSucceedsWithValue(nice));
// Set -20 < niceval < 0
nice = -12;
EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), nice), SyscallSucceeds());
errno = 0;
EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
SyscallSucceedsWithValue(nice));
}
// Verify that priority/niceness are exposed via /proc/PID/stat.
TEST(SetpriorityTest, NicenessExposedViaProcfs) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
constexpr int kNiceVal = 12;
ASSERT_THAT(setpriority(PRIO_PROCESS, getpid(), kNiceVal), SyscallSucceeds());
errno = 0;
ASSERT_THAT(getpriority(PRIO_PROCESS, getpid()),
SyscallSucceedsWithValue(kNiceVal));
// Now verify we can read that same value via /proc/self/stat.
std::string proc_stat;
ASSERT_NO_ERRNO(GetContents("/proc/self/stat", &proc_stat));
std::vector<std::string> pieces = absl::StrSplit(proc_stat, ' ');
ASSERT_GT(pieces.size(), 20);
int niceness_procfs = 0;
ASSERT_TRUE(absl::SimpleAtoi(pieces[18], &niceness_procfs));
EXPECT_EQ(niceness_procfs, kNiceVal);
}
// In the kernel's implementation, values outside the range of [-20, 19] are
// truncated to these minimum and maximum values. See
// https://elixir.bootlin.com/linux/v4.4/source/kernel/sys.c#L190
TEST(SetpriorityTest, OutsideRange) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
// Set niceval > 19
EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/100),
SyscallSucceeds());
errno = 0;
// Test niceval truncated to 19
EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
SyscallSucceedsWithValue(/*maxnice=*/19));
// Set niceval < -20
EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/-100),
SyscallSucceeds());
errno = 0;
// Test niceval truncated to -20
EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
SyscallSucceedsWithValue(/*minnice=*/-20));
}
// Process is not found when which=PRIO_PROCESS
TEST(SetpriorityTest, InvalidWho) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
// Flaky, but it's tough to avoid a race condition when finding an unused pid
EXPECT_THAT(setpriority(PRIO_PROCESS,
/*who=*/INT_MAX - 1,
/*nice=*/16),
SyscallFailsWithErrno(ESRCH));
}
// Nice succeeds, correctly modifies (or in this case does not
// modify priority of process
TEST(SetpriorityTest, NiceSucceeds) {
errno = 0;
const int priority_before = getpriority(PRIO_PROCESS, /*who=*/0);
ASSERT_THAT(nice(/*inc=*/0), SyscallSucceeds());
// nice(0) should not change priority
EXPECT_EQ(priority_before, getpriority(PRIO_PROCESS, /*who=*/0));
}
// Threads resulting from clone() maintain parent's priority
// Changes to child priority do not affect parent's priority
TEST(GetpriorityTest, CloneMaintainsPriority) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
constexpr int kParentPriority = 16;
constexpr int kChildPriority = 14;
ASSERT_THAT(setpriority(PRIO_PROCESS, getpid(), kParentPriority),
SyscallSucceeds());
ScopedThread th([]() {
// Check that priority equals that of parent thread
pid_t my_tid;
EXPECT_THAT(my_tid = syscall(__NR_gettid), SyscallSucceeds());
EXPECT_THAT(getpriority(PRIO_PROCESS, my_tid),
SyscallSucceedsWithValue(kParentPriority));
// Change the child thread's priority
EXPECT_THAT(setpriority(PRIO_PROCESS, my_tid, kChildPriority),
SyscallSucceeds());
});
th.Join();
// Check that parent's priority reemained the same even though
// the child's priority was altered
EXPECT_EQ(kParentPriority, getpriority(PRIO_PROCESS, syscall(__NR_gettid)));
}
} // namespace
} // namespace testing
} // namespace gvisor