mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement command GETNCNT for semctl.
PiperOrigin-RevId: 341154192
This commit is contained in:
@@ -103,6 +103,7 @@ type waiter struct {
|
||||
waiterEntry
|
||||
|
||||
// value represents how much resource the waiter needs to wake up.
|
||||
// The value is either 0 or negative.
|
||||
value int16
|
||||
ch chan struct{}
|
||||
}
|
||||
@@ -423,8 +424,7 @@ func (s *Set) GetPID(num int32, creds *auth.Credentials) (int32, error) {
|
||||
return sem.pid, nil
|
||||
}
|
||||
|
||||
// GetZeroWaiters returns number of waiters waiting for the sem to go to zero.
|
||||
func (s *Set) GetZeroWaiters(num int32, creds *auth.Credentials) (uint16, error) {
|
||||
func (s *Set) countWaiters(num int32, creds *auth.Credentials, pred func(w *waiter) bool) (uint16, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
@@ -437,13 +437,27 @@ func (s *Set) GetZeroWaiters(num int32, creds *auth.Credentials) (uint16, error)
|
||||
if sem == nil {
|
||||
return 0, syserror.ERANGE
|
||||
}
|
||||
var semzcnt uint16
|
||||
var cnt uint16
|
||||
for w := sem.waiters.Front(); w != nil; w = w.Next() {
|
||||
if w.value == 0 {
|
||||
semzcnt++
|
||||
if pred(w) {
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
return semzcnt, nil
|
||||
return cnt, nil
|
||||
}
|
||||
|
||||
// CountZeroWaiters returns number of waiters waiting for the sem's value to increase.
|
||||
func (s *Set) CountZeroWaiters(num int32, creds *auth.Credentials) (uint16, error) {
|
||||
return s.countWaiters(num, creds, func(w *waiter) bool {
|
||||
return w.value == 0
|
||||
})
|
||||
}
|
||||
|
||||
// CountNegativeWaiters returns number of waiters waiting for the sem to go to zero.
|
||||
func (s *Set) CountNegativeWaiters(num int32, creds *auth.Credentials) (uint16, error) {
|
||||
return s.countWaiters(num, creds, func(w *waiter) bool {
|
||||
return w.value < 0
|
||||
})
|
||||
}
|
||||
|
||||
// ExecuteOps attempts to execute a list of operations to the set. It only
|
||||
@@ -598,11 +612,18 @@ func (s *Set) destroy() {
|
||||
}
|
||||
}
|
||||
|
||||
func abs(val int16) int16 {
|
||||
if val < 0 {
|
||||
return -val
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// wakeWaiters goes over all waiters and checks which of them can be notified.
|
||||
func (s *sem) wakeWaiters() {
|
||||
// Note that this will release all waiters waiting for 0 too.
|
||||
for w := s.waiters.Front(); w != nil; {
|
||||
if s.value < w.value {
|
||||
if s.value < abs(w.value) {
|
||||
// Still blocked, skip it.
|
||||
w = w.Next()
|
||||
continue
|
||||
|
||||
@@ -118,7 +118,7 @@ var AMD64 = &kernel.SyscallTable{
|
||||
63: syscalls.Supported("uname", Uname),
|
||||
64: syscalls.Supported("semget", Semget),
|
||||
65: syscalls.PartiallySupported("semop", Semop, "Option SEM_UNDO not supported.", nil),
|
||||
66: syscalls.PartiallySupported("semctl", Semctl, "Options IPC_INFO, SEM_INFO, SEM_STAT, SEM_STAT_ANY, GETNCNT not supported.", nil),
|
||||
66: syscalls.PartiallySupported("semctl", Semctl, "Options IPC_INFO, SEM_INFO, SEM_STAT, SEM_STAT_ANY not supported.", nil),
|
||||
67: syscalls.Supported("shmdt", Shmdt),
|
||||
68: syscalls.ErrorWithEvent("msgget", syserror.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921)
|
||||
69: syscalls.ErrorWithEvent("msgsnd", syserror.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921)
|
||||
@@ -619,7 +619,7 @@ var ARM64 = &kernel.SyscallTable{
|
||||
188: syscalls.ErrorWithEvent("msgrcv", syserror.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921)
|
||||
189: syscalls.ErrorWithEvent("msgsnd", syserror.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921)
|
||||
190: syscalls.Supported("semget", Semget),
|
||||
191: syscalls.PartiallySupported("semctl", Semctl, "Options IPC_INFO, SEM_INFO, SEM_STAT, SEM_STAT_ANY, GETNCNT not supported.", nil),
|
||||
191: syscalls.PartiallySupported("semctl", Semctl, "Options IPC_INFO, SEM_INFO, SEM_STAT, SEM_STAT_ANY not supported.", nil),
|
||||
192: syscalls.ErrorWithEvent("semtimedop", syserror.ENOSYS, "", []string{"gvisor.dev/issue/137"}),
|
||||
193: syscalls.PartiallySupported("semop", Semop, "Option SEM_UNDO not supported.", nil),
|
||||
194: syscalls.PartiallySupported("shmget", Shmget, "Option SHM_HUGETLB is not supported.", nil),
|
||||
|
||||
@@ -139,14 +139,17 @@ func Semctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal
|
||||
return 0, nil, err
|
||||
|
||||
case linux.GETZCNT:
|
||||
v, err := getSemzcnt(t, id, num)
|
||||
v, err := getZCnt(t, id, num)
|
||||
return uintptr(v), nil, err
|
||||
|
||||
case linux.GETNCNT:
|
||||
v, err := getNCnt(t, id, num)
|
||||
return uintptr(v), nil, err
|
||||
|
||||
case linux.IPC_INFO,
|
||||
linux.SEM_INFO,
|
||||
linux.SEM_STAT,
|
||||
linux.SEM_STAT_ANY,
|
||||
linux.GETNCNT:
|
||||
linux.SEM_STAT_ANY:
|
||||
|
||||
t.Kernel().EmitUnimplementedEvent(t)
|
||||
fallthrough
|
||||
@@ -262,12 +265,22 @@ func getPID(t *kernel.Task, id int32, num int32) (int32, error) {
|
||||
return int32(tg.ID()), nil
|
||||
}
|
||||
|
||||
func getSemzcnt(t *kernel.Task, id int32, num int32) (uint16, error) {
|
||||
func getZCnt(t *kernel.Task, id int32, num int32) (uint16, error) {
|
||||
r := t.IPCNamespace().SemaphoreRegistry()
|
||||
set := r.FindByID(id)
|
||||
if set == nil {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
creds := auth.CredentialsFromContext(t)
|
||||
return set.GetZeroWaiters(num, creds)
|
||||
return set.CountZeroWaiters(num, creds)
|
||||
}
|
||||
|
||||
func getNCnt(t *kernel.Task, id int32, num int32) (uint16, error) {
|
||||
r := t.IPCNamespace().SemaphoreRegistry()
|
||||
set := r.FindByID(id)
|
||||
if set == nil {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
creds := auth.CredentialsFromContext(t)
|
||||
return set.CountNegativeWaiters(num, creds)
|
||||
}
|
||||
|
||||
@@ -543,14 +543,14 @@ TEST(SemaphoreTest, SemCtlIpcStat) {
|
||||
SyscallFailsWithErrno(EACCES));
|
||||
}
|
||||
|
||||
// Calls semctl(GETZCNT) until the returned value is >= target, an internal
|
||||
// timeout expires, or semctl returns an error.
|
||||
PosixErrorOr<int> WaitSemzcnt(int semid, int target) {
|
||||
// Calls semctl(semid, 0, cmd) until the returned value is >= target, an
|
||||
// internal timeout expires, or semctl returns an error.
|
||||
PosixErrorOr<int> WaitSemctl(int semid, int target, int cmd) {
|
||||
constexpr absl::Duration timeout = absl::Seconds(10);
|
||||
const auto deadline = absl::Now() + timeout;
|
||||
int semcnt = 0;
|
||||
while (absl::Now() < deadline) {
|
||||
semcnt = semctl(semid, 0, GETZCNT);
|
||||
semcnt = semctl(semid, 0, cmd);
|
||||
if (semcnt < 0) {
|
||||
return PosixError(errno, "semctl(GETZCNT) failed");
|
||||
}
|
||||
@@ -594,7 +594,9 @@ TEST(SemaphoreTest, SemopGetzcnt) {
|
||||
}
|
||||
children.push_back(child_pid);
|
||||
}
|
||||
EXPECT_THAT(WaitSemzcnt(sem.get(), kLoops), IsPosixErrorOkAndHolds(kLoops));
|
||||
|
||||
EXPECT_THAT(WaitSemctl(sem.get(), kLoops, GETZCNT),
|
||||
IsPosixErrorOkAndHolds(kLoops));
|
||||
// Set semval to 0, which wakes up children that sleep on the semop.
|
||||
ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 0), SyscallSucceeds());
|
||||
for (const auto& child_pid : children) {
|
||||
@@ -625,7 +627,7 @@ TEST(SemaphoreTest, SemopGetzcntOnSetRemoval) {
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
EXPECT_THAT(WaitSemzcnt(semid, 1), IsPosixErrorOkAndHolds(1));
|
||||
EXPECT_THAT(WaitSemctl(semid, 1, GETZCNT), IsPosixErrorOkAndHolds(1));
|
||||
// Remove the semaphore set, which fails the sleep semop.
|
||||
ASSERT_THAT(semctl(semid, 0, IPC_RMID), SyscallSucceeds());
|
||||
int status;
|
||||
@@ -654,7 +656,8 @@ TEST(SemaphoreTest, SemopGetzcntOnSignal_NoRandomSave) {
|
||||
TEST_PCHECK(semop(sem.get(), &buf, 1) < 0 && errno == EINTR);
|
||||
_exit(0);
|
||||
}
|
||||
EXPECT_THAT(WaitSemzcnt(sem.get(), 1), IsPosixErrorOkAndHolds(1));
|
||||
|
||||
EXPECT_THAT(WaitSemctl(sem.get(), 1, GETZCNT), IsPosixErrorOkAndHolds(1));
|
||||
// Send a signal to the child, which fails the sleep semop.
|
||||
ASSERT_EQ(kill(child_pid, SIGHUP), 0);
|
||||
|
||||
@@ -667,6 +670,109 @@ TEST(SemaphoreTest, SemopGetzcntOnSignal_NoRandomSave) {
|
||||
EXPECT_EQ(semctl(sem.get(), 0, GETZCNT), 0);
|
||||
}
|
||||
|
||||
TEST(SemaphoreTest, SemopGetncnt) {
|
||||
// Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
|
||||
ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
|
||||
// Create a write only semaphore set.
|
||||
AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT));
|
||||
ASSERT_THAT(sem.get(), SyscallSucceeds());
|
||||
|
||||
// No read permission to retrieve semzcnt.
|
||||
EXPECT_THAT(semctl(sem.get(), 0, GETNCNT), SyscallFailsWithErrno(EACCES));
|
||||
|
||||
// Remove the calling thread's read permission.
|
||||
struct semid_ds ds = {};
|
||||
ds.sem_perm.uid = getuid();
|
||||
ds.sem_perm.gid = getgid();
|
||||
ds.sem_perm.mode = 0600;
|
||||
ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &ds), SyscallSucceeds());
|
||||
|
||||
std::vector<pid_t> children;
|
||||
|
||||
struct sembuf buf = {};
|
||||
buf.sem_num = 0;
|
||||
buf.sem_op = -1;
|
||||
constexpr size_t kLoops = 10;
|
||||
for (auto i = 0; i < kLoops; i++) {
|
||||
auto child_pid = fork();
|
||||
if (child_pid == 0) {
|
||||
TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0);
|
||||
_exit(0);
|
||||
}
|
||||
children.push_back(child_pid);
|
||||
}
|
||||
EXPECT_THAT(WaitSemctl(sem.get(), kLoops, GETNCNT),
|
||||
IsPosixErrorOkAndHolds(kLoops));
|
||||
// Set semval to 1, which wakes up children that sleep on the semop.
|
||||
ASSERT_THAT(semctl(sem.get(), 0, SETVAL, kLoops), SyscallSucceeds());
|
||||
for (const auto& child_pid : children) {
|
||||
int status;
|
||||
ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
|
||||
SyscallSucceedsWithValue(child_pid));
|
||||
EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||
}
|
||||
EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0);
|
||||
}
|
||||
|
||||
TEST(SemaphoreTest, SemopGetncntOnSetRemoval) {
|
||||
auto semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT);
|
||||
ASSERT_THAT(semid, SyscallSucceeds());
|
||||
ASSERT_EQ(semctl(semid, 0, GETNCNT), 0);
|
||||
|
||||
auto child_pid = fork();
|
||||
if (child_pid == 0) {
|
||||
struct sembuf buf = {};
|
||||
buf.sem_num = 0;
|
||||
buf.sem_op = -1;
|
||||
|
||||
// Ensure that wait will only unblock when the semaphore is removed. On
|
||||
// EINTR retry it may race with deletion and return EINVAL
|
||||
TEST_PCHECK(RetryEINTR(semop)(semid, &buf, 1) < 0 &&
|
||||
(errno == EIDRM || errno == EINVAL));
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
EXPECT_THAT(WaitSemctl(semid, 1, GETNCNT), IsPosixErrorOkAndHolds(1));
|
||||
// Remove the semaphore set, which fails the sleep semop.
|
||||
ASSERT_THAT(semctl(semid, 0, IPC_RMID), SyscallSucceeds());
|
||||
int status;
|
||||
ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
|
||||
SyscallSucceedsWithValue(child_pid));
|
||||
EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||
EXPECT_THAT(semctl(semid, 0, GETNCNT), SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
TEST(SemaphoreTest, SemopGetncntOnSignal_NoRandomSave) {
|
||||
AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
|
||||
ASSERT_THAT(sem.get(), SyscallSucceeds());
|
||||
ASSERT_EQ(semctl(sem.get(), 0, GETNCNT), 0);
|
||||
|
||||
// Saving will cause semop() to be spuriously interrupted.
|
||||
DisableSave ds;
|
||||
|
||||
auto child_pid = fork();
|
||||
if (child_pid == 0) {
|
||||
TEST_PCHECK(signal(SIGHUP, [](int sig) -> void {}) != SIG_ERR);
|
||||
struct sembuf buf = {};
|
||||
buf.sem_num = 0;
|
||||
buf.sem_op = -1;
|
||||
|
||||
TEST_PCHECK(semop(sem.get(), &buf, 1) < 0 && errno == EINTR);
|
||||
_exit(0);
|
||||
}
|
||||
EXPECT_THAT(WaitSemctl(sem.get(), 1, GETNCNT), IsPosixErrorOkAndHolds(1));
|
||||
// Send a signal to the child, which fails the sleep semop.
|
||||
ASSERT_EQ(kill(child_pid, SIGHUP), 0);
|
||||
|
||||
ds.reset();
|
||||
|
||||
int status;
|
||||
ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
|
||||
SyscallSucceedsWithValue(child_pid));
|
||||
EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||
EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
Reference in New Issue
Block a user