mirror of
https://github.com/microsoft/WSL.git
synced 2025-07-04 07:43:21 +00:00
1421 lines
43 KiB
C
1421 lines
43 KiB
C
![]() |
/*++
|
||
|
|
||
|
Copyright (c) Microsoft. All rights reserved.
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
sem.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
This file is a test for the system V semaphore family of system calls.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <unistd.h>
|
||
|
#include <sys/eventfd.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/xattr.h>
|
||
|
#include <sys/mman.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <sys/un.h>
|
||
|
#include <sys/ipc.h>
|
||
|
#include <sys/shm.h>
|
||
|
#include <sys/sem.h>
|
||
|
#include <sys/prctl.h>
|
||
|
#include <sys/wait.h>
|
||
|
#include <grp.h>
|
||
|
#include <netinet/in.h>
|
||
|
#include <netdb.h>
|
||
|
#include <time.h>
|
||
|
#include <linux/random.h>
|
||
|
|
||
|
#if !defined(__amd64__) && !defined(__aarch64__)
|
||
|
|
||
|
#include <sys/capability.h>
|
||
|
|
||
|
#else
|
||
|
|
||
|
#include <sys/cdefs.h>
|
||
|
#include <linux/capability.h>
|
||
|
|
||
|
#define _LINUX_CAPABILITY_VERSION_3 0x20080522
|
||
|
|
||
|
#ifndef O_PATH
|
||
|
#define O_PATH 010000000
|
||
|
#endif
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#include "lxtcommon.h"
|
||
|
#include "unittests.h"
|
||
|
|
||
|
#define LXT_NAME "sem"
|
||
|
|
||
|
#define SEM_ACCESS_UID 1004
|
||
|
#define SEM_ACCESS_GID 1004
|
||
|
#define SEM_COUNT (10)
|
||
|
|
||
|
//
|
||
|
// Globals.
|
||
|
//
|
||
|
|
||
|
bool g_VerboseSem = true;
|
||
|
|
||
|
int SemCtlSyscall(PLXT_ARGS Args);
|
||
|
|
||
|
int SemGetSyscall(PLXT_ARGS Args);
|
||
|
|
||
|
int SemOpFlags(PLXT_ARGS Args);
|
||
|
|
||
|
int SemOpSyscall(PLXT_ARGS Args);
|
||
|
|
||
|
void SemPrintInfo(struct semid_ds* Stat);
|
||
|
|
||
|
static const LXT_VARIATION g_LxtVariations[] = {
|
||
|
{"semget syscall", SemGetSyscall}, {"semctl syscall", SemCtlSyscall}, {"semop syscall", SemOpSyscall}, {"semop flags", SemOpFlags}};
|
||
|
|
||
|
int SemTestEntry(int Argc, char* Argv[])
|
||
|
{
|
||
|
|
||
|
LXT_ARGS Args;
|
||
|
int Result;
|
||
|
|
||
|
LxtCheckResult(LxtInitialize(Argc, Argv, &Args, LXT_NAME));
|
||
|
LXT_SYNCHRONIZATION_POINT_INIT();
|
||
|
LxtCheckResult(LxtRunVariations(&Args, g_LxtVariations, LXT_COUNT_OF(g_LxtVariations)));
|
||
|
|
||
|
ErrorExit:
|
||
|
LxtUninitialize();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int SemCtlSyscall(PLXT_ARGS Args)
|
||
|
|
||
|
{
|
||
|
|
||
|
struct __user_cap_data_struct CapData[2];
|
||
|
struct __user_cap_header_struct CapHeader;
|
||
|
int ChildPid;
|
||
|
gid_t Gid;
|
||
|
int Id;
|
||
|
int Index;
|
||
|
struct semid_ds OldStat;
|
||
|
int Result;
|
||
|
struct seminfo SemInfo;
|
||
|
struct semid_ds Stat;
|
||
|
uid_t Uid;
|
||
|
unsigned short Values[SEM_COUNT];
|
||
|
|
||
|
ChildPid = -1;
|
||
|
Uid = getuid();
|
||
|
Gid = getgid();
|
||
|
LxtCheckErrno(Id = LxtSemGet(IPC_PRIVATE, SEM_COUNT, (IPC_CREAT | IPC_EXCL)));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, SEM_STAT, &Stat));
|
||
|
LxtCheckEqual(SEM_COUNT, Stat.sem_nsems, "%Iu");
|
||
|
LxtCheckEqual(Uid, Stat.sem_perm.uid, "%d");
|
||
|
LxtCheckEqual(Gid, Stat.sem_perm.gid, "%d");
|
||
|
LxtCheckEqual(Uid, Stat.sem_perm.cuid, "%d");
|
||
|
LxtCheckEqual(Gid, Stat.sem_perm.cgid, "%d");
|
||
|
LxtCheckNotEqual(0, Stat.sem_ctime, "%Iu");
|
||
|
LxtCheckEqual(0, Stat.sem_otime, "%Iu");
|
||
|
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_STAT, &Stat));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_SET, &Stat));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_INFO, &SemInfo));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_INFO, &SemInfo));
|
||
|
LxtCheckErrno(LxtSemCtl(0, 0, IPC_INFO, &SemInfo));
|
||
|
LxtCheckErrno(LxtSemCtl(1, 0, IPC_INFO, &SemInfo));
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETPID, NULL), "%d");
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, SEM_COUNT, GETPID, NULL), EINVAL);
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, SEM_COUNT, GETVAL, NULL), EINVAL);
|
||
|
memset(&Values, 0, sizeof(Values));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, GETALL, &Values));
|
||
|
for (Index = 0; Index < SEM_COUNT; Index += 1)
|
||
|
{
|
||
|
LxtCheckEqual(Values[Index], LxtSemCtl(Id, Index, GETVAL, NULL), "%d");
|
||
|
}
|
||
|
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETNCNT, NULL), "%d");
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETZCNT, NULL), "%d");
|
||
|
|
||
|
//
|
||
|
// Check GETPID and GETVAL again after doing a setval on a single semaphore.
|
||
|
//
|
||
|
|
||
|
Values[0] = 1;
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, SETVAL, Values[0]));
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, SEM_COUNT, SETVAL, Values[0]), EINVAL);
|
||
|
LxtCheckEqual(getpid(), LxtSemCtl(Id, 0, GETPID, NULL), "%d");
|
||
|
LxtCheckEqual(Values[0], LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, GETALL, &Values));
|
||
|
for (Index = 0; Index < SEM_COUNT; Index += 1)
|
||
|
{
|
||
|
LxtCheckEqual(Values[Index], LxtSemCtl(Id, Index, GETVAL, NULL), "%d");
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Verify the pid and value of the other semaphores has not changed.
|
||
|
//
|
||
|
|
||
|
for (Index = 1; Index < SEM_COUNT; Index += 1)
|
||
|
{
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, Index, GETPID, NULL), "%d");
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, Index, GETVAL, NULL), "%d");
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// SETALL command.
|
||
|
//
|
||
|
|
||
|
for (Index = 0; Index < SEM_COUNT; Index += 1)
|
||
|
{
|
||
|
Values[0] = Index;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Ensure that each semaphore's value has been updated. Interestingly the
|
||
|
// lastpid value is not updated by the SETALL command.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, SETALL, &Values));
|
||
|
for (Index = 0; Index < SEM_COUNT; Index += 1)
|
||
|
{
|
||
|
if (Index == 0)
|
||
|
{
|
||
|
LxtCheckEqual(getpid(), LxtSemCtl(Id, Index, GETPID, NULL), "%d");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, Index, GETPID, NULL), "%d");
|
||
|
}
|
||
|
|
||
|
LxtCheckEqual(Values[Index], LxtSemCtl(Id, Index, GETVAL, NULL), "%d");
|
||
|
}
|
||
|
|
||
|
memset(Values, 0, sizeof(Values));
|
||
|
Values[1] = -1;
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, SETALL, &Values), ERANGE);
|
||
|
|
||
|
//
|
||
|
// Create a child without the CAP_IPC_OWNER capability.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(ChildPid = fork());
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// Drop the CAP_IPC_OWNER capability.
|
||
|
//
|
||
|
|
||
|
memset(&CapHeader, 0, sizeof(CapHeader));
|
||
|
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
|
||
|
LxtCheckErrno(LxtCapGet(&CapHeader, CapData)) LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
|
||
|
CapData[CAP_TO_INDEX(CAP_IPC_OWNER)].permitted &= ~CAP_TO_MASK(CAP_IPC_OWNER);
|
||
|
CapData[0].effective = CapData[0].permitted;
|
||
|
CapData[1].effective = CapData[1].permitted;
|
||
|
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
|
||
|
|
||
|
//
|
||
|
// Verify commands that requires the IPC_OWNER capability now fail.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, SEM_STAT, &Stat), EACCES);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, IPC_STAT, &Stat), EACCES);
|
||
|
|
||
|
//
|
||
|
// Change the UID and verify commands fail.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(setuid(SEM_ACCESS_UID));
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, IPC_SET, &Stat), EPERM);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, IPC_RMID, NULL), EPERM);
|
||
|
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, IPC_STAT, &Stat), EACCES);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, SEM_STAT, &Stat), EACCES);
|
||
|
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_INFO, &SemInfo));
|
||
|
LxtCheckErrno(LxtSemCtl(0, 0, IPC_INFO, &SemInfo));
|
||
|
Result = LXT_RESULT_SUCCESS;
|
||
|
goto ErrorExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Wait for the child to exit.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
|
||
|
|
||
|
//
|
||
|
// Invalid parameter variations.
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// Ensure IPC_SET cannot set invalid mode bits (they are silently ignored).
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_STAT, &Stat));
|
||
|
Stat.sem_perm.mode = -1;
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_SET, &Stat));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_STAT, &Stat));
|
||
|
LxtCheckEqual(Stat.sem_perm.mode, 0777, "%o");
|
||
|
|
||
|
//
|
||
|
// Ensure the uid and gid cannot be set to -1.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_STAT, &OldStat));
|
||
|
Stat = OldStat;
|
||
|
Stat.sem_perm.uid = -1;
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, IPC_SET, &Stat), EINVAL);
|
||
|
Stat = OldStat;
|
||
|
Stat.sem_perm.gid = -1;
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, IPC_SET, &Stat), EINVAL);
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_STAT, &Stat));
|
||
|
LxtCheckEqual(Stat.sem_perm.uid, OldStat.sem_perm.uid, "%d");
|
||
|
LxtCheckEqual(Stat.sem_perm.gid, OldStat.sem_perm.gid, "%d");
|
||
|
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, -1, GETPID, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, SEM_COUNT, GETPID, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, -1, GETVAL, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, SEM_COUNT, GETVAL, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, -1, SETVAL, 0), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, SEM_COUNT, SETVAL, 0), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, -1, GETNCNT, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, SEM_COUNT, GETNCNT, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, -1, GETZCNT, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, SEM_COUNT, GETZCNT, NULL), EINVAL);
|
||
|
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(-1, 0, SEM_STAT, &Stat), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(-1, 0, IPC_STAT, &Stat), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(-1, 0, IPC_SET, &Stat), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, IPC_INFO, NULL), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, IPC_INFO, -1), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(0, 0, IPC_INFO, NULL), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(0, 0, IPC_INFO, -1), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(-1, 0, GETPID, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, SEM_COUNT, GETPID, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(-1, 0, GETVAL, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, SEM_COUNT, GETVAL, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, GETALL, NULL), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, GETALL, -1), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(-1, 0, GETNCNT, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(-1, 0, GETZCNT, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, SETALL, NULL), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, SETALL, -1), EFAULT);
|
||
|
|
||
|
ErrorExit:
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
_exit(Result);
|
||
|
}
|
||
|
|
||
|
if (Id != -1)
|
||
|
{
|
||
|
LxtSemCtl(Id, 0, IPC_RMID, NULL);
|
||
|
}
|
||
|
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
int SemGetSyscall(PLXT_ARGS Args)
|
||
|
|
||
|
{
|
||
|
|
||
|
struct __user_cap_data_struct CapData[2];
|
||
|
struct __user_cap_header_struct CapHeader;
|
||
|
int ChildPid;
|
||
|
int Id;
|
||
|
key_t Key;
|
||
|
int Mode;
|
||
|
size_t Result;
|
||
|
struct semid_ds Stat;
|
||
|
time_t Time;
|
||
|
|
||
|
ChildPid = -1;
|
||
|
Id = -1;
|
||
|
|
||
|
//
|
||
|
// Create a key, verify that creating the key with the IPC_EXCL flag fails.
|
||
|
//
|
||
|
|
||
|
Mode = 0000;
|
||
|
LxtLogInfo("Mode %o", Mode);
|
||
|
LxtCheckErrno(LxtGetrandom(&Key, sizeof(Key), 0));
|
||
|
LxtLogInfo("Key = %u", Key);
|
||
|
LxtCheckErrno(Id = LxtSemGet(Key, SEM_COUNT, (IPC_CREAT | IPC_EXCL | Mode)));
|
||
|
LxtLogInfo("Id = %d", Id);
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_STAT, &Stat));
|
||
|
SemPrintInfo(&Stat);
|
||
|
LxtCheckEqual(Key, Stat.sem_perm.__key, "%Iu");
|
||
|
LxtCheckEqual(SEM_COUNT, Stat.sem_nsems, "%Iu");
|
||
|
LxtCheckEqual(0, Stat.sem_otime, "%Iu");
|
||
|
LxtCheckNotEqual(0, Stat.sem_ctime, "%Iu");
|
||
|
LxtCheckEqual(Mode, Stat.sem_perm.mode, "%o");
|
||
|
LxtCheckEqual(getuid(), Stat.sem_perm.cuid, "%d");
|
||
|
LxtCheckEqual(getuid(), Stat.sem_perm.uid, "%d");
|
||
|
LxtCheckEqual(getgid(), Stat.sem_perm.cgid, "%d");
|
||
|
LxtCheckEqual(getgid(), Stat.sem_perm.gid, "%d");
|
||
|
|
||
|
//
|
||
|
// semget with IPC_CREAT or IPC_EXCL when the region already exists.
|
||
|
//
|
||
|
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, IPC_CREAT), "%Iu");
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, IPC_EXCL), "%Iu");
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, 0), "%Iu");
|
||
|
|
||
|
//
|
||
|
// semget with count = 0 should succeed.
|
||
|
//
|
||
|
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, 0, 0), "%Iu");
|
||
|
|
||
|
//
|
||
|
// Create a child with a different uid and gid that does not have the
|
||
|
// IPC_OWNER capability.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(ChildPid = fork());
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
|
||
|
LxtCheckErrno(setgid(SEM_ACCESS_GID));
|
||
|
LxtCheckErrno(setuid(SEM_ACCESS_UID));
|
||
|
memset(&CapData, 0, sizeof(CapData));
|
||
|
memset(&CapHeader, 0, sizeof(CapHeader));
|
||
|
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
|
||
|
CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID);
|
||
|
CapData[CAP_TO_INDEX(CAP_IPC_OWNER)].permitted |= CAP_TO_MASK(CAP_IPC_OWNER);
|
||
|
CapData[0].effective = CapData[0].permitted;
|
||
|
CapData[1].effective = CapData[1].permitted;
|
||
|
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
|
||
|
|
||
|
//
|
||
|
// These should succeed because the child still has the IPC_OWNER cap.
|
||
|
//
|
||
|
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, IPC_CREAT), "%Iu");
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, IPC_EXCL), "%Iu");
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, 0777), "%Iu");
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, 0666), "%Iu");
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, 0600), "%Iu");
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, 0060), "%Iu");
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, 0006), "%Iu");
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, 0), "%Iu");
|
||
|
|
||
|
//
|
||
|
// Drop all group membership and the CAP_IPC_OWNER capability and
|
||
|
// attempt to call semget with unmatching mode bits.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(Result = setgroups(0, NULL));
|
||
|
memset(&CapData, 0, sizeof(CapData));
|
||
|
memset(&CapHeader, 0, sizeof(CapHeader));
|
||
|
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
|
||
|
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
|
||
|
LxtCheckErrnoFailure(LxtSemGet(Key, SEM_COUNT, 0777), EACCES);
|
||
|
LxtCheckErrnoFailure(LxtSemGet(Key, SEM_COUNT, 0666), EACCES);
|
||
|
LxtCheckErrnoFailure(LxtSemGet(Key, SEM_COUNT, 0600), EACCES);
|
||
|
LxtCheckErrnoFailure(LxtSemGet(Key, SEM_COUNT, 0060), EACCES);
|
||
|
LxtCheckErrnoFailure(LxtSemGet(Key, SEM_COUNT, 0006), EACCES);
|
||
|
|
||
|
//
|
||
|
// Use the same permission as before, these should succeed.
|
||
|
//
|
||
|
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, IPC_CREAT), "%Iu");
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, IPC_EXCL), "%Iu");
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, 0), "%Iu");
|
||
|
goto ErrorExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Wait for the child to exit.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
|
||
|
|
||
|
//
|
||
|
// Invalid parameter variations.
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// semget with IPC_CREAT | IPC_EXCL when the region already exists, should
|
||
|
// succeed with only IPC_EXCL.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrnoFailure(LxtSemGet(Key, SEM_COUNT, (IPC_CREAT | IPC_EXCL)), EEXIST);
|
||
|
|
||
|
//
|
||
|
// semget with a known key and a size that does not match.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrnoFailure(LxtSemGet(Key, (SEM_COUNT * 2), 0), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemGet(Key, SEM_COUNT + 1, 0), EINVAL);
|
||
|
|
||
|
//
|
||
|
// N.B. There appears to be no error checking for invalid flags, only the
|
||
|
// presence of valid flags.
|
||
|
//
|
||
|
// -1 includes the IPC_EXCL flag so this shoudl return EEXIST.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrnoFailure(LxtSemGet(Key, SEM_COUNT, -1), EEXIST);
|
||
|
LxtCheckEqual(Id, LxtSemGet(Key, SEM_COUNT, (-1 & ~IPC_EXCL)), "%Iu");
|
||
|
|
||
|
//
|
||
|
// Delete the region and create a new one with a size of one byte.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_RMID, &Stat));
|
||
|
LxtCheckErrnoFailure(LxtSemCtl(Id, 0, IPC_RMID, NULL), EINVAL);
|
||
|
Id = -1;
|
||
|
LxtCheckErrno(Id = LxtSemGet(IPC_PRIVATE, 1, 0));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_STAT, &Stat));
|
||
|
LxtCheckEqual(1, Stat.sem_nsems, "%Iu");
|
||
|
|
||
|
//
|
||
|
// Delete the region and create a new region with a size of zero bytes
|
||
|
// (should fail).
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_RMID, &Stat));
|
||
|
Id = -1;
|
||
|
LxtCheckErrnoFailure(Id = LxtSemGet(IPC_PRIVATE, 0, 0), EINVAL);
|
||
|
|
||
|
ErrorExit:
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
_exit(Result);
|
||
|
}
|
||
|
|
||
|
if (Id != -1)
|
||
|
{
|
||
|
LxtSemCtl(Id, 0, IPC_RMID, NULL);
|
||
|
}
|
||
|
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
int SemCloneChild(void* Param)
|
||
|
{
|
||
|
|
||
|
int Id;
|
||
|
int Result;
|
||
|
|
||
|
Id = *((int*)Param);
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 1, GETVAL, NULL), "%d");
|
||
|
LxtCheckErrno(unshare(CLONE_SYSVSEM));
|
||
|
|
||
|
//
|
||
|
// Verify the values did not change.
|
||
|
//
|
||
|
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 1, GETVAL, NULL), "%d");
|
||
|
Result = LXT_RESULT_SUCCESS;
|
||
|
|
||
|
ErrorExit:
|
||
|
exit(Result);
|
||
|
}
|
||
|
|
||
|
int SemCloneThread(void* Param)
|
||
|
{
|
||
|
|
||
|
long long Data;
|
||
|
int Event;
|
||
|
int Result;
|
||
|
|
||
|
Event = *((int*)Param);
|
||
|
LxtCheckErrno(read(Event, &Data, sizeof(Data)));
|
||
|
|
||
|
//
|
||
|
// Just exit the thread, not the thread group, on success.
|
||
|
//
|
||
|
|
||
|
syscall(SYS_exit, 0);
|
||
|
|
||
|
ErrorExit:
|
||
|
exit(Result);
|
||
|
}
|
||
|
|
||
|
int SemOpFlags(PLXT_ARGS Args)
|
||
|
{
|
||
|
int ChildPid;
|
||
|
LXT_CLONE_ARGS CloneArgs;
|
||
|
long long EventData;
|
||
|
int Flags;
|
||
|
int Id;
|
||
|
struct sembuf Operations[SEM_COUNT];
|
||
|
size_t Result;
|
||
|
char* SharedStack;
|
||
|
int SharedEvent;
|
||
|
pid_t SharedTid;
|
||
|
struct semid_ds Stat;
|
||
|
int StackSize;
|
||
|
int Status;
|
||
|
char* UnsharedStack;
|
||
|
int UnsharedEvent;
|
||
|
pid_t UnsharedTid;
|
||
|
unsigned short Values[SEM_COUNT];
|
||
|
|
||
|
ChildPid = -1;
|
||
|
EventData = 1;
|
||
|
Id = -1;
|
||
|
SharedEvent = -1;
|
||
|
SharedStack = NULL;
|
||
|
UnsharedEvent = -1;
|
||
|
UnsharedStack = NULL;
|
||
|
memset(Operations, 0, sizeof(Operations));
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT_START();
|
||
|
|
||
|
//
|
||
|
// Create a semaphore set.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(Id = LxtSemGet(IPC_PRIVATE, SEM_COUNT, (IPC_CREAT | IPC_EXCL)));
|
||
|
|
||
|
//
|
||
|
// Test the nowait flag.
|
||
|
//
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = -1;
|
||
|
Operations[0].sem_flg = IPC_NOWAIT;
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, Operations, 1), EAGAIN);
|
||
|
|
||
|
//
|
||
|
// Increment the first semaphore.
|
||
|
//
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 1;
|
||
|
Operations[0].sem_flg = 0;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 1));
|
||
|
|
||
|
//
|
||
|
// Create a child.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(ChildPid = fork());
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// Decrement the first semaphore and increment the second semaphore,
|
||
|
// both with the undo flag set.
|
||
|
//
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = -1;
|
||
|
Operations[0].sem_flg = SEM_UNDO;
|
||
|
Operations[1].sem_num = 1;
|
||
|
Operations[1].sem_op = 1;
|
||
|
Operations[1].sem_flg = SEM_UNDO;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 2));
|
||
|
goto ErrorExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Wait for the child to exit.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
|
||
|
|
||
|
//
|
||
|
// Ensure the child's operations were undone.
|
||
|
//
|
||
|
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 1, GETVAL, NULL), "%d");
|
||
|
|
||
|
//
|
||
|
// Ensure the wait can still be satisfied.
|
||
|
//
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = -1;
|
||
|
Operations[0].sem_flg = 0;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 1));
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
|
||
|
//
|
||
|
// Create a child.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(ChildPid = fork());
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// Set the first semaphore to the max with the undo flag specified and
|
||
|
// lower the count without the undo flag specified.
|
||
|
//
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 0x7fff;
|
||
|
Operations[0].sem_flg = SEM_UNDO;
|
||
|
Operations[1].sem_num = 0;
|
||
|
Operations[1].sem_op = -0x7fff;
|
||
|
Operations[1].sem_flg = 0;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 2));
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 1;
|
||
|
Operations[0].sem_flg = 0;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 1));
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
goto ErrorExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Wait for child to perform first operation.
|
||
|
//
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
|
||
|
//
|
||
|
// Wait for child to perform second operation.
|
||
|
//
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
|
||
|
//
|
||
|
// Wait for the child to exit and ensure the count does not drop below
|
||
|
// zero.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
|
||
|
//
|
||
|
// Create a child.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(ChildPid = fork());
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// Set the first semaphore to the max without the undo flag specified and
|
||
|
// lower the count with the undo flag specified.
|
||
|
//
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 0x7fff;
|
||
|
Operations[0].sem_flg = 0;
|
||
|
Operations[1].sem_num = 0;
|
||
|
Operations[1].sem_op = -0x7fff;
|
||
|
Operations[1].sem_flg = SEM_UNDO;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 2));
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 1;
|
||
|
Operations[0].sem_flg = 0;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 1));
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
goto ErrorExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Wait for child to perform first operation.
|
||
|
//
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
|
||
|
//
|
||
|
// Wait for child to perform second operation.
|
||
|
//
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
|
||
|
//
|
||
|
// Wait for the child to exit and ensure the count does not exceed the max
|
||
|
// semaphore value.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
|
||
|
LxtCheckEqual(0x7fff, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, SETVAL, 0));
|
||
|
|
||
|
//
|
||
|
// Validate semctl SETVAL clears undo adjustments.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(ChildPid = fork());
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// Set the first semaphore to the max with the undo flag specified and
|
||
|
// lower the count without the undo flag specified.
|
||
|
//
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 0x7fff;
|
||
|
Operations[0].sem_flg = 0;
|
||
|
Operations[1].sem_num = 0;
|
||
|
Operations[1].sem_op = -0x7fff;
|
||
|
Operations[1].sem_flg = SEM_UNDO;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 2));
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 1;
|
||
|
Operations[0].sem_flg = 0;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 1));
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
goto ErrorExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Wait for child to perform first operation.
|
||
|
//
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
|
||
|
//
|
||
|
// Wait for child to perform second operation and set the smeaphore value
|
||
|
// to zero. This should remove the pending semaphore adjustment.
|
||
|
//
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, SETVAL, 0));
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
|
||
|
//
|
||
|
// Wait for the child to exit and ensure the adjustment was not applied.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
|
||
|
//
|
||
|
// Create a child, verify when the child unshares the semaphore adjustments
|
||
|
// are cleared.
|
||
|
//
|
||
|
|
||
|
memset(Values, 0, sizeof(Values));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, SETALL, &Values));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 1, SETVAL, 1));
|
||
|
LxtCheckErrno(ChildPid = fork());
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// Increment one semaphore and decrement another both with the undo
|
||
|
// flag set.
|
||
|
//
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 1;
|
||
|
Operations[0].sem_flg = SEM_UNDO;
|
||
|
Operations[1].sem_num = 1;
|
||
|
Operations[1].sem_op = -1;
|
||
|
Operations[1].sem_flg = SEM_UNDO;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 2));
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 1, GETVAL, NULL), "%d");
|
||
|
LxtCheckErrno(unshare(CLONE_SYSVSEM));
|
||
|
|
||
|
//
|
||
|
// Ensure the state was undone.
|
||
|
//
|
||
|
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 1, GETVAL, NULL), "%d");
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
goto ErrorExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Wait for child to unshare.
|
||
|
//
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
|
||
|
//
|
||
|
// Ensure the child's operations were undone.
|
||
|
//
|
||
|
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 1, GETVAL, NULL), "%d");
|
||
|
LXT_SYNCHRONIZATION_POINT();
|
||
|
|
||
|
//
|
||
|
// Wait for the child to exit.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
|
||
|
|
||
|
//
|
||
|
// Reset semaphore state.
|
||
|
//
|
||
|
|
||
|
memset(Values, 0, sizeof(Values));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, SETALL, &Values));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 1, SETVAL, 1));
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 1;
|
||
|
Operations[0].sem_flg = SEM_UNDO;
|
||
|
Operations[1].sem_num = 1;
|
||
|
Operations[1].sem_op = -1;
|
||
|
Operations[1].sem_flg = SEM_UNDO;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 2));
|
||
|
|
||
|
//
|
||
|
// Clone a child to share the same SystemV semaphore adjustment structure.
|
||
|
//
|
||
|
|
||
|
LxtCheckResult(LxtClone(SemCloneChild, &Id, CLONE_SYSVSEM | SIGCHLD, &CloneArgs));
|
||
|
|
||
|
//
|
||
|
// Wait for child to exit.
|
||
|
//
|
||
|
|
||
|
LxtCheckResult(LxtWaitPidPoll(CloneArgs.CloneId, 0));
|
||
|
|
||
|
//
|
||
|
// Values should not have changed yet.
|
||
|
//
|
||
|
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 1, GETVAL, NULL), "%d");
|
||
|
|
||
|
//
|
||
|
// Create two threads, one sharing the sempahore adjustment structure
|
||
|
// and one not.
|
||
|
//
|
||
|
|
||
|
Flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
|
||
|
|
||
|
StackSize = 1024 * 1024;
|
||
|
|
||
|
LxtCheckErrno(SharedEvent = eventfd(0, EFD_SEMAPHORE));
|
||
|
SharedStack = malloc(StackSize);
|
||
|
LxtCheckResult(clone(SemCloneThread, SharedStack + StackSize, Flags | CLONE_SYSVSEM, &SharedEvent, &SharedTid, NULL, &SharedTid));
|
||
|
|
||
|
LxtCheckErrno(UnsharedEvent = eventfd(0, EFD_SEMAPHORE));
|
||
|
UnsharedStack = malloc(StackSize);
|
||
|
LxtCheckResult(clone(SemCloneThread, UnsharedStack + StackSize, Flags, &UnsharedEvent, &UnsharedTid, NULL, &UnsharedTid));
|
||
|
|
||
|
//
|
||
|
// Unshare; since there is still a thread sharing, adjustments should
|
||
|
// not occur.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(unshare(CLONE_SYSVSEM));
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 1, GETVAL, NULL), "%d");
|
||
|
|
||
|
//
|
||
|
// Signal the sharing thread and wait for it to exit; adjustments should
|
||
|
// occur shortly thereafter.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(write(SharedEvent, &EventData, sizeof(EventData)));
|
||
|
LxtCheckErrno(LxtJoinThread(&SharedTid));
|
||
|
usleep(100000);
|
||
|
LxtCheckEqual(0, LxtSemCtl(Id, 0, GETVAL, NULL), "%d");
|
||
|
LxtCheckEqual(1, LxtSemCtl(Id, 1, GETVAL, NULL), "%d");
|
||
|
|
||
|
//
|
||
|
// Signal the unshared thread to clean things up.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(write(UnsharedEvent, &EventData, sizeof(EventData)));
|
||
|
LxtCheckErrno(LxtJoinThread(&UnsharedTid));
|
||
|
|
||
|
ErrorExit:
|
||
|
LXT_SYNCHRONIZATION_POINT_END();
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
_exit(Result);
|
||
|
}
|
||
|
|
||
|
if (Id != -1)
|
||
|
{
|
||
|
LxtSemCtl(Id, 0, IPC_RMID, NULL);
|
||
|
}
|
||
|
|
||
|
if (SharedEvent != -1)
|
||
|
{
|
||
|
close(SharedEvent);
|
||
|
}
|
||
|
|
||
|
if (UnsharedEvent != -1)
|
||
|
{
|
||
|
close(SharedEvent);
|
||
|
}
|
||
|
|
||
|
free(SharedStack);
|
||
|
free(UnsharedStack);
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
int SemOpSyscall(PLXT_ARGS Args)
|
||
|
|
||
|
{
|
||
|
struct __user_cap_data_struct CapData[2];
|
||
|
struct __user_cap_header_struct CapHeader;
|
||
|
int ChildPid;
|
||
|
int Id;
|
||
|
int Index;
|
||
|
int Mode;
|
||
|
struct sembuf Operations[SEM_COUNT];
|
||
|
size_t Result;
|
||
|
struct semid_ds Stat;
|
||
|
int Status;
|
||
|
time_t Time;
|
||
|
struct timespec Timeout;
|
||
|
unsigned short Values[SEM_COUNT];
|
||
|
|
||
|
ChildPid = -1;
|
||
|
Id = -1;
|
||
|
memset(Operations, 0, sizeof(Operations));
|
||
|
memset(Values, 0, sizeof(Values));
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT_START();
|
||
|
|
||
|
//
|
||
|
// Create a semaphore with zero mode bits.
|
||
|
//
|
||
|
|
||
|
Mode = 0000;
|
||
|
LxtLogInfo("Mode %o", Mode);
|
||
|
LxtCheckErrno(Id = LxtSemGet(IPC_PRIVATE, SEM_COUNT, (IPC_CREAT | IPC_EXCL | Mode)));
|
||
|
LxtLogInfo("Id = %d", Id);
|
||
|
|
||
|
//
|
||
|
// Create a child with a different uid and gid that does not have the
|
||
|
// IPC_OWNER capability.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(ChildPid = fork());
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
|
||
|
LxtCheckErrno(setgid(SEM_ACCESS_GID));
|
||
|
LxtCheckErrno(setuid(SEM_ACCESS_UID));
|
||
|
memset(&CapData, 0, sizeof(CapData));
|
||
|
memset(&CapHeader, 0, sizeof(CapHeader));
|
||
|
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
|
||
|
CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID);
|
||
|
CapData[CAP_TO_INDEX(CAP_IPC_OWNER)].permitted |= CAP_TO_MASK(CAP_IPC_OWNER);
|
||
|
CapData[0].effective = CapData[0].permitted;
|
||
|
CapData[1].effective = CapData[1].permitted;
|
||
|
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
|
||
|
|
||
|
//
|
||
|
// These should succeed because the child still has the IPC_OWNER cap.
|
||
|
//
|
||
|
|
||
|
memset(Operations, 0, sizeof(Operations));
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, SEM_COUNT));
|
||
|
|
||
|
//
|
||
|
// Drop all group membership and the CAP_IPC_OWNER capability and
|
||
|
// attempt to call semget with unmatching mode bits.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(Result = setgroups(0, NULL));
|
||
|
memset(&CapData, 0, sizeof(CapData));
|
||
|
memset(&CapHeader, 0, sizeof(CapHeader));
|
||
|
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
|
||
|
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
|
||
|
|
||
|
//
|
||
|
// Attempt to issue operations, these should fail.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, Operations, SEM_COUNT), EACCES);
|
||
|
goto ErrorExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Wait for the child to exit.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
|
||
|
|
||
|
//
|
||
|
// Create a new readable semaphore.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_RMID, NULL));
|
||
|
Mode = 0004;
|
||
|
LxtLogInfo("Mode %o", Mode);
|
||
|
LxtCheckErrno(Id = LxtSemGet(IPC_PRIVATE, SEM_COUNT, (IPC_CREAT | IPC_EXCL | Mode)));
|
||
|
LxtLogInfo("Id = %d", Id);
|
||
|
|
||
|
//
|
||
|
// Create a child with a different uid and gid that does not have the
|
||
|
// IPC_OWNER capability.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(ChildPid = fork());
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
|
||
|
LxtCheckErrno(setgid(SEM_ACCESS_GID));
|
||
|
LxtCheckErrno(setuid(SEM_ACCESS_UID));
|
||
|
memset(&CapData, 0, sizeof(CapData));
|
||
|
memset(&CapHeader, 0, sizeof(CapHeader));
|
||
|
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
|
||
|
CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID);
|
||
|
CapData[CAP_TO_INDEX(CAP_IPC_OWNER)].permitted |= CAP_TO_MASK(CAP_IPC_OWNER);
|
||
|
CapData[0].effective = CapData[0].permitted;
|
||
|
CapData[1].effective = CapData[1].permitted;
|
||
|
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
|
||
|
|
||
|
//
|
||
|
// These should succeed because the child still has the IPC_OWNER cap.
|
||
|
//
|
||
|
|
||
|
memset(Operations, 0, sizeof(Operations));
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, SEM_COUNT));
|
||
|
|
||
|
//
|
||
|
// Drop all group membership and the CAP_IPC_OWNER capability and
|
||
|
// attempt to call semget with unmatching mode bits.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(Result = setgroups(0, NULL));
|
||
|
memset(&CapData, 0, sizeof(CapData));
|
||
|
memset(&CapHeader, 0, sizeof(CapHeader));
|
||
|
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
|
||
|
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
|
||
|
|
||
|
//
|
||
|
// Attempt to issue a "wait for zero" operation, this should succeed
|
||
|
// and return immediately because the value is zero.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, SEM_COUNT));
|
||
|
|
||
|
//
|
||
|
// Attempt to increment the semaphore, this should fail.
|
||
|
//
|
||
|
|
||
|
Operations[1].sem_num = 0;
|
||
|
Operations[1].sem_op = 1;
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, &Operations[1], 1), EACCES);
|
||
|
|
||
|
//
|
||
|
// Attempt to decrement the semaphore, this should fail.
|
||
|
//
|
||
|
|
||
|
Operations[2].sem_num = 0;
|
||
|
Operations[2].sem_op = -1;
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, &Operations[2], 1), EACCES);
|
||
|
|
||
|
//
|
||
|
// Attempt the increment and wait operations after a wait for zero that
|
||
|
// succeeds.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, Operations, 3), EACCES);
|
||
|
goto ErrorExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Wait for the child to exit.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
|
||
|
|
||
|
//
|
||
|
// Create a new writable semaphore.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, IPC_RMID, NULL));
|
||
|
Mode = 0002;
|
||
|
LxtLogInfo("Mode %o", Mode);
|
||
|
LxtCheckErrno(Id = LxtSemGet(IPC_PRIVATE, SEM_COUNT, (IPC_CREAT | IPC_EXCL | Mode)));
|
||
|
LxtLogInfo("Id = %d", Id);
|
||
|
|
||
|
//
|
||
|
// Create a child with a different uid and gid that does not have the
|
||
|
// IPC_OWNER capability.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(ChildPid = fork());
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
LxtCheckErrno(prctl(PR_SET_KEEPCAPS, 1));
|
||
|
LxtCheckErrno(setgid(SEM_ACCESS_GID));
|
||
|
LxtCheckErrno(setuid(SEM_ACCESS_UID));
|
||
|
memset(&CapData, 0, sizeof(CapData));
|
||
|
memset(&CapHeader, 0, sizeof(CapHeader));
|
||
|
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
|
||
|
CapData[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID);
|
||
|
CapData[CAP_TO_INDEX(CAP_IPC_OWNER)].permitted |= CAP_TO_MASK(CAP_IPC_OWNER);
|
||
|
CapData[0].effective = CapData[0].permitted;
|
||
|
CapData[1].effective = CapData[1].permitted;
|
||
|
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
|
||
|
|
||
|
//
|
||
|
// These should succeed because the child still has the IPC_OWNER cap.
|
||
|
//
|
||
|
|
||
|
memset(Operations, 0, sizeof(Operations));
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, SEM_COUNT));
|
||
|
|
||
|
//
|
||
|
// Drop all group membership and the CAP_IPC_OWNER capability and
|
||
|
// attempt to call semget with unmatching mode bits.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(Result = setgroups(0, NULL));
|
||
|
memset(&CapData, 0, sizeof(CapData));
|
||
|
memset(&CapHeader, 0, sizeof(CapHeader));
|
||
|
CapHeader.version = _LINUX_CAPABILITY_VERSION_3;
|
||
|
LxtCheckErrno(LxtCapSet(&CapHeader, CapData));
|
||
|
|
||
|
//
|
||
|
// Attempt to issue a "wait for zero" operation, this should fail.
|
||
|
//
|
||
|
|
||
|
memset(Operations, 0, sizeof(Operations));
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, Operations, SEM_COUNT), EACCES);
|
||
|
|
||
|
//
|
||
|
// Attempt to increment the semaphore, this should succeed.
|
||
|
//
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 1;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 1));
|
||
|
|
||
|
//
|
||
|
// Attempt to decrement the semaphore, this should succeed.
|
||
|
//
|
||
|
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = -1;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 1));
|
||
|
|
||
|
//
|
||
|
// Fill the operations buffer with a combination of valid operations
|
||
|
// and operations that the caller does not have permission to do. The
|
||
|
// parent will verify the the semaphore values are adjusted correctly.
|
||
|
//
|
||
|
|
||
|
memset(Operations, 0, sizeof(Operations));
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 1;
|
||
|
|
||
|
Operations[1].sem_num = 1;
|
||
|
Operations[1].sem_op = 1;
|
||
|
|
||
|
Operations[2].sem_num = 2;
|
||
|
Operations[2].sem_op = 0;
|
||
|
|
||
|
Operations[3].sem_num = 3;
|
||
|
Operations[3].sem_op = 1;
|
||
|
|
||
|
Operations[4].sem_num = 2;
|
||
|
Operations[4].sem_op = 0;
|
||
|
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 3));
|
||
|
LxtCheckErrno(LxtSemOp(Id, &Operations[1], 2));
|
||
|
LxtCheckErrno(LxtSemOp(Id, &Operations[1], 3));
|
||
|
LxtCheckErrno(LxtSemOp(Id, &Operations[2], 2));
|
||
|
LxtCheckErrno(LxtSemOp(Id, &Operations[2], 3));
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, &Operations[2], 1), EACCES);
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (1)
|
||
|
|
||
|
//
|
||
|
// Wait for parent to query.
|
||
|
//
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (2)
|
||
|
|
||
|
//
|
||
|
// Test how overflow is handled. It looks like there is a per-semaphore
|
||
|
// rolling count that is checked before any operations are performed.
|
||
|
//
|
||
|
|
||
|
memset(Operations, 0, sizeof(Operations));
|
||
|
Operations[0].sem_op = 32767;
|
||
|
Operations[1].sem_op = 1;
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 1));
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, &Operations[1], 1), ERANGE);
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (3)
|
||
|
|
||
|
//
|
||
|
// Wait for parent to query.
|
||
|
//
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (4)
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, Operations, 2), ERANGE);
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (5)
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (6)
|
||
|
memset(Operations, 0, sizeof(Operations));
|
||
|
Operations[0].sem_op = 32767;
|
||
|
Operations[1].sem_op = -1;
|
||
|
Operations[2].sem_op = 2;
|
||
|
Operations[3].sem_op = -1;
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, Operations, 4), ERANGE);
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (7)
|
||
|
|
||
|
memset(Operations, 0, sizeof(Operations));
|
||
|
Operations[0].sem_op = -1;
|
||
|
Operations[1].sem_op = 32767;
|
||
|
Operations[2].sem_op = 1;
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (8)
|
||
|
LxtLogInfo("child semop");
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, Operations, 4), ERANGE);
|
||
|
LxtLogInfo("child return");
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (9)
|
||
|
goto ErrorExit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Wait for the child to do the fisrt semop and query the values.
|
||
|
//
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (1)
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, GETALL, &Values));
|
||
|
LxtCheckEqual(1, Values[0], "%u");
|
||
|
LxtCheckEqual(3, Values[1], "%u");
|
||
|
LxtCheckEqual(3, Values[3], "%u");
|
||
|
memset(Values, 0, sizeof(Values));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, SETALL, &Values));
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (2)
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (3)
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, GETALL, &Values));
|
||
|
LxtCheckEqual(32767, Values[0], "%u");
|
||
|
memset(Values, 0, sizeof(Values));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, SETALL, &Values));
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (4)
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (5)
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, GETALL, &Values));
|
||
|
LxtCheckEqual(0, Values[0], "%u");
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (6)
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, GETALL, &Values));
|
||
|
LxtCheckEqual(0, Values[0], "%u");
|
||
|
memset(Values, 0, sizeof(Values));
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, SETALL, &Values));
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (7)
|
||
|
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (8)
|
||
|
Operations[0].sem_num = 0;
|
||
|
Operations[0].sem_op = 1;
|
||
|
sleep(1);
|
||
|
LxtCheckErrno(LxtSemOp(Id, Operations, 1));
|
||
|
LXT_SYNCHRONIZATION_POINT(); // (9)
|
||
|
LxtCheckErrno(LxtSemCtl(Id, 0, GETALL, &Values));
|
||
|
LxtCheckEqual(1, Values[0], "%u");
|
||
|
|
||
|
//
|
||
|
// Wait for the child to exit.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrno(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
|
||
|
|
||
|
//
|
||
|
// Invalid parameter variations.
|
||
|
//
|
||
|
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, NULL, 0), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, NULL, 501), E2BIG);
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, NULL, 1), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemOp(Id, -1, 1), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemOp(-1, NULL, 0), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemOp(-1, NULL, 1), EINVAL);
|
||
|
|
||
|
LxtCheckErrnoFailure(LxtSemTimedOp(Id, NULL, 0, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemTimedOp(Id, NULL, 501, NULL), E2BIG);
|
||
|
LxtCheckErrnoFailure(LxtSemTimedOp(Id, NULL, 1, NULL), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemTimedOp(Id, -1, 1, NULL), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemTimedOp(Id, Operations, 1, -1), EFAULT);
|
||
|
LxtCheckErrnoFailure(LxtSemTimedOp(-1, NULL, 0, NULL), EINVAL);
|
||
|
LxtCheckErrnoFailure(LxtSemTimedOp(-1, NULL, 1, -1), EINVAL);
|
||
|
Timeout.tv_sec = 0;
|
||
|
Timeout.tv_nsec = 999999999 + 1;
|
||
|
LxtCheckErrnoFailure(LxtSemTimedOp(Id, Operations, 1, &Timeout), EINVAL);
|
||
|
Timeout.tv_sec = -1;
|
||
|
Timeout.tv_nsec = 0;
|
||
|
LxtCheckErrnoFailure(LxtSemTimedOp(Id, Operations, 1, &Timeout), EINVAL);
|
||
|
|
||
|
ErrorExit:
|
||
|
LXT_SYNCHRONIZATION_POINT_END();
|
||
|
if (ChildPid == 0)
|
||
|
{
|
||
|
_exit(Result);
|
||
|
}
|
||
|
|
||
|
if (Id != -1)
|
||
|
{
|
||
|
LxtSemCtl(Id, 0, IPC_RMID, NULL);
|
||
|
}
|
||
|
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
void SemPrintInfo(struct semid_ds* Stat)
|
||
|
|
||
|
{
|
||
|
|
||
|
if (g_VerboseSem == false)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
LxtLogInfo("sem_perm.__key %u", Stat->sem_perm.__key);
|
||
|
LxtLogInfo("sem_perm.uid %u", Stat->sem_perm.uid);
|
||
|
LxtLogInfo("sem_perm.gid %u", Stat->sem_perm.gid);
|
||
|
LxtLogInfo("sem_perm.cuid %u", Stat->sem_perm.cuid);
|
||
|
LxtLogInfo("sem_perm.cgid %u", Stat->sem_perm.cgid);
|
||
|
LxtLogInfo("sem_perm.mode %o", Stat->sem_perm.mode);
|
||
|
LxtLogInfo("sem_perm.__seq %d", Stat->sem_perm.__seq);
|
||
|
LxtLogInfo("sem_otime %Iu", Stat->sem_otime);
|
||
|
LxtLogInfo("sem_ctime %Iu", Stat->sem_ctime);
|
||
|
LxtLogInfo("sem_nsems %Iu", Stat->sem_nsems);
|
||
|
return;
|
||
|
}
|