Implement command GETZCNT for semctl.

PiperOrigin-RevId: 340389884
This commit is contained in:
Jing Chen
2020-11-02 23:58:45 -08:00
committed by gVisor bot
parent 1321f837bd
commit 1a3f417f4a
4 changed files with 155 additions and 4 deletions
+23
View File
@@ -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.
//
+2 -2
View File
@@ -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),
+15 -2
View File
@@ -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)
}
+115
View File
@@ -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