mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement command GETZCNT for semctl.
PiperOrigin-RevId: 340389884
This commit is contained in:
@@ -423,6 +423,29 @@ 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) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// The calling process must have read permission on the semaphore set.
|
||||
if !s.checkPerms(creds, fs.PermMask{Read: true}) {
|
||||
return 0, syserror.EACCES
|
||||
}
|
||||
|
||||
sem := s.findSem(num)
|
||||
if sem == nil {
|
||||
return 0, syserror.ERANGE
|
||||
}
|
||||
var semzcnt uint16
|
||||
for w := sem.waiters.Front(); w != nil; w = w.Next() {
|
||||
if w.value == 0 {
|
||||
semzcnt++
|
||||
}
|
||||
}
|
||||
return semzcnt, nil
|
||||
}
|
||||
|
||||
// ExecuteOps attempts to execute a list of operations to the set. It only
|
||||
// succeeds when all operations can be applied. No changes are made if it fails.
|
||||
//
|
||||
|
||||
@@ -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, GETZCNT not supported.", nil),
|
||||
66: syscalls.PartiallySupported("semctl", Semctl, "Options IPC_INFO, SEM_INFO, SEM_STAT, SEM_STAT_ANY, GETNCNT 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, GETZCNT not supported.", nil),
|
||||
191: syscalls.PartiallySupported("semctl", Semctl, "Options IPC_INFO, SEM_INFO, SEM_STAT, SEM_STAT_ANY, GETNCNT 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),
|
||||
|
||||
@@ -138,12 +138,15 @@ func Semctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal
|
||||
|
||||
return 0, nil, err
|
||||
|
||||
case linux.GETZCNT:
|
||||
v, err := getSemzcnt(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.GETZCNT:
|
||||
linux.GETNCNT:
|
||||
|
||||
t.Kernel().EmitUnimplementedEvent(t)
|
||||
fallthrough
|
||||
@@ -258,3 +261,13 @@ 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) {
|
||||
r := t.IPCNamespace().SemaphoreRegistry()
|
||||
set := r.FindByID(id)
|
||||
if set == nil {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
creds := auth.CredentialsFromContext(t)
|
||||
return set.GetZeroWaiters(num, creds)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <signal.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/sem.h>
|
||||
#include <sys/types.h>
|
||||
@@ -542,6 +543,120 @@ TEST(SemaphoreTest, SemCtlIpcStat) {
|
||||
SyscallFailsWithErrno(EACCES));
|
||||
}
|
||||
|
||||
// The funcion keeps calling semctl's GETZCNT command until
|
||||
// the return value is not less than target.
|
||||
int WaitSemzcnt(int semid, int target) {
|
||||
constexpr absl::Duration timeout = absl::Seconds(10);
|
||||
int semcnt = 0;
|
||||
for (auto start = absl::Now(); absl::Now() - start < timeout;) {
|
||||
semcnt = semctl(semid, 0, GETZCNT);
|
||||
if (semcnt >= target) {
|
||||
break;
|
||||
}
|
||||
absl::SleepFor(absl::Milliseconds(10));
|
||||
}
|
||||
return semcnt;
|
||||
}
|
||||
|
||||
TEST(SemaphoreTest, SemopGetzcnt) {
|
||||
// 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, GETZCNT), 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;
|
||||
ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds());
|
||||
|
||||
struct sembuf buf = {};
|
||||
buf.sem_num = 0;
|
||||
buf.sem_op = 0;
|
||||
constexpr size_t kLoops = 10;
|
||||
for (auto i = 0; i < kLoops; i++) {
|
||||
auto child_pid = fork();
|
||||
if (child_pid == 0) {
|
||||
ASSERT_THAT(RetryEINTR(semop)(sem.get(), &buf, 1), SyscallSucceeds());
|
||||
_exit(0);
|
||||
}
|
||||
children.push_back(child_pid);
|
||||
}
|
||||
EXPECT_THAT(WaitSemzcnt(sem.get(), kLoops), SyscallSucceedsWithValue(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) {
|
||||
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, GETZCNT), 0);
|
||||
}
|
||||
|
||||
TEST(SemaphoreTest, SemopGetzcntOnSetRemoval) {
|
||||
auto semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT);
|
||||
ASSERT_THAT(semid, SyscallSucceeds());
|
||||
ASSERT_THAT(semctl(semid, 0, SETVAL, 1), SyscallSucceeds());
|
||||
ASSERT_EQ(semctl(semid, 0, GETZCNT), 0);
|
||||
|
||||
auto child_pid = fork();
|
||||
if (child_pid == 0) {
|
||||
struct sembuf buf = {};
|
||||
buf.sem_num = 0;
|
||||
buf.sem_op = 0;
|
||||
|
||||
ASSERT_THAT(RetryEINTR(semop)(semid, &buf, 1), SyscallFails());
|
||||
// Ensure that wait will only unblock when the semaphore is removed. On
|
||||
// EINTR retry it may race with deletion and return EINVAL.
|
||||
ASSERT_TRUE(errno == EIDRM || errno == EINVAL) << "errno=" << errno;
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
EXPECT_THAT(WaitSemzcnt(semid, 1), SyscallSucceedsWithValue(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, GETZCNT), SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
TEST(SemaphoreTest, SemopGetzcntOnSignal) {
|
||||
AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
|
||||
ASSERT_THAT(sem.get(), SyscallSucceeds());
|
||||
ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds());
|
||||
ASSERT_EQ(semctl(sem.get(), 0, GETZCNT), 0);
|
||||
|
||||
auto child_pid = fork();
|
||||
if (child_pid == 0) {
|
||||
signal(SIGHUP, [](int sig) -> void {});
|
||||
struct sembuf buf = {};
|
||||
buf.sem_num = 0;
|
||||
buf.sem_op = 0;
|
||||
|
||||
ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EINTR));
|
||||
_exit(0);
|
||||
}
|
||||
EXPECT_THAT(WaitSemzcnt(sem.get(), 1), SyscallSucceedsWithValue(1));
|
||||
// Send a signal to the child, which fails the sleep semop.
|
||||
ASSERT_EQ(kill(child_pid, SIGHUP), 0);
|
||||
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, GETZCNT), 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
Reference in New Issue
Block a user