#include "namespace.h"
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <err.h>
#include <pthread.h>
#include <unistd.h>
#include "un-namespace.h"
#include "sysvipc_lock.h"
#include "sysvipc_ipc.h"
#include "sysvipc_sockets.h"
#include "sysvipc_shm.h"
#include "sysvipc_hash.h"
#define SYSV_MUTEX_LOCK(x) if (__isthreaded) _pthread_mutex_lock(x)
#define SYSV_MUTEX_UNLOCK(x) if (__isthreaded) _pthread_mutex_unlock(x)
#define SYSV_MUTEX_DESTROY(x) if (__isthreaded) _pthread_mutex_destroy(x)
struct hashtable *shmres = NULL;
struct hashtable *shmaddrs = NULL;
pthread_mutex_t lock_resources = PTHREAD_MUTEX_INITIALIZER;
extern int daemon_fd;
extern struct sem_undo *undos;
static int
shminit(void)
{
if (shmres) {
errno = EPERM;
return (-1);
}
shmres = _hash_init(MAXSIZE);
if (!shmres)
goto out_resources;
shmaddrs = _hash_init(MAXSIZE);
if (!shmaddrs)
goto out_addrs;
return 0;
out_addrs:
_hash_destroy(shmres);
out_resources:
return -1;
}
static int
shmcheck(void)
{
int ret;
if ((ret = sysvinit()) != 0)
return (ret);
if ((ret = shminit()) < 0)
return (ret);
return (0);
}
static int
is_shm_started(void)
{
if (!is_sysvinit())
return (0);
if (!shmres)
return (0);
return (1);
}
int
_shmget(key_t key, size_t size, int shmflg, int type)
{
struct shmget_msg msg;
struct shm_data *data;
int shmid, fd;
int flags;
SYSV_MUTEX_LOCK(&lock_resources);
if (shmcheck() < 0) {
sysv_print_err("init sysv ipc\n");
goto done;
}
msg.key = key;
msg.size = size;
msg.shmflg = shmflg;
msg.type = type;
send_message(daemon_fd, type, (char *)&msg, sizeof(msg));
fd = receive_fd(daemon_fd);
if (fd < 0) {
shmid = -1;
goto done;
}
flags = _fcntl(fd, F_GETFD, 0);
if (_fcntl(fd, F_SETFD, flags & FD_CLOEXEC) == -1) {
sysv_print_err("fcntl error\n");
shmid = -1;
goto done;
}
receive_message(daemon_fd, (char *)&shmid, sizeof(shmid));
if (shmid < 0) {
errno = -shmid;
shmid = -1;
goto done;
}
data = _hash_lookup(shmres, shmid);
if (data)
goto done;
data = malloc(sizeof(struct shm_data));
data->fd = fd;
data->size = size;
data->shmid = shmid;
data->type = type;
data->used = 0;
data->removed = 0;
data->access = 0;
_hash_insert(shmres, shmid, data);
done:
SYSV_MUTEX_UNLOCK(&lock_resources);
return (shmid);
}
int
sysvipc_shmget(key_t key, size_t size, int shmflg)
{
return (_shmget(key, size, shmflg, SHMGET));
}
void *
sysvipc_shmat(int shmid, const void *shmaddr, int shmflg)
{
struct shmat_msg msg;
void *addr = NULL;
int error;
int flags, prot;
size_t size;
struct shm_data *data;
SYSV_MUTEX_LOCK(&lock_resources);
if (!is_shm_started()) {
errno = EINVAL;
goto done;
}
data = _hash_lookup(shmres, shmid);
if (data == NULL) {
errno = EINVAL;
goto done;
}
size = round_page(data->size);
#ifdef VM_PROT_READ_IS_EXEC
prot = PROT_READ | PROT_EXECUTE;
#else
prot = PROT_READ;
#endif
if ((shmflg & SHM_RDONLY) == 0)
prot |= PROT_WRITE;
flags = MAP_SHARED;
if (shmaddr) {
if (shmflg & SHM_RND) {
addr = (void *)(rounddown2((uintptr_t)shmaddr, SHMLBA));
} else if (((uintptr_t)shmaddr & (SHMLBA-1)) == 0) {
addr = __DECONST(void *, shmaddr);
} else {
errno = EINVAL;
goto done;
}
}
msg.shmid = shmid;
msg.shmaddr = shmaddr;
msg.shmflg = shmflg;
msg.size = data->size;
send_message(daemon_fd, SHMAT, (char *)&msg, sizeof(msg));
receive_message(daemon_fd, (char *)&error, sizeof(error));
if (error) {
errno = error;
goto done;
}
addr = mmap(addr, size, prot, flags, data->fd, 0);
if (!addr) {
sysv_print_err("mmap\n");
send_message(daemon_fd, SHMDT, (char *)&shmid, sizeof(shmid));
goto done;
}
data->internal = addr;
_hash_insert(shmaddrs, (u_long)addr, data);
done:
SYSV_MUTEX_UNLOCK(&lock_resources);
return (addr);
}
static
void shmremove(int shmid)
{
struct shm_data *data;
data = _hash_remove(shmres, shmid);
_close(data->fd);
free(data);
data = NULL;
}
int
sysvipc_shmctl(int shmid, int cmd, struct shmid_ds *buf)
{
int size, ret;
struct shmctl_msg *msg;
SYSV_MUTEX_LOCK(&lock_resources);
ret = -1;
if (!is_shm_started()) {
errno = EINVAL;
goto done;
}
size = sizeof(struct shmctl_msg);
msg = malloc(size);
msg->shmid = shmid;
msg->cmd = cmd;
if (cmd == IPC_SET)
msg->buf = *buf;
send_message(daemon_fd, SHMCTL, (char *)msg, sizeof(*msg));
receive_message(daemon_fd, (char *)&ret, sizeof(ret));
if (ret == 0 && cmd == IPC_STAT)
receive_message(daemon_fd, (char *)buf, sizeof(*buf));
if (ret == 0 && cmd == IPC_RMID)
shmremove(shmid);
errno = ret;
done:
SYSV_MUTEX_UNLOCK(&lock_resources);
return (ret == 0 ? 0 : -1);
}
static int
_shmdt(const void *shmaddr, int send_to_daemon)
{
int ret;
size_t size;
struct shm_data *data;
ret = -1;
SYSV_MUTEX_LOCK(&lock_resources);
if (!is_shm_started()) {
errno = EINVAL;
goto done;
}
data = _hash_remove(shmaddrs, (u_long)shmaddr);
if (data == NULL) {
errno = EINVAL;
goto done;
}
size = round_page(data->size);
ret = munmap(__DECONST(void *, shmaddr), size);
if (ret)
goto done;
if (send_to_daemon)
send_message(daemon_fd, SHMDT, (char *)&data->shmid, sizeof(int));
shmaddr = NULL;
free(data);
data = NULL;
done:
SYSV_MUTEX_UNLOCK(&lock_resources);
return (ret);
}
int
sysvipc_shmdt(const void *shmaddr)
{
return (_shmdt(shmaddr, 1));
}
void
shmchild(void)
{
int i;
struct entries_list *list;
struct hashentry *tmp, *ttmp;
struct shmat_msg msg;
struct shm_data *data;
int error;
for (i=0; i<get_hash_size(MAXSIZE); i++) {
list = &shmaddrs->entries[i];
if (LIST_EMPTY(list))
continue;
LIST_FOREACH_MUTABLE(tmp, list, entry_link, ttmp) {
data = (struct shm_data*)tmp->value;
if (data->type == UNDOGET) {
continue;
}
msg.shmid = data->shmid;
msg.shmaddr = data->internal;
msg.shmflg = 0;
msg.size = data->size;
send_message(daemon_fd, SHMAT, (char *)&msg, sizeof(msg));
receive_message(daemon_fd, (char *)&error, sizeof(error));
if (error) {
errno = error;
_shmdt(data->internal, 0);
shmremove(data->shmid);
sysv_print_err(" %d shmchild\n", error);
sleep(20);
}
}
}
data = _hash_remove(shmaddrs, (u_long)undos);
if (undos) {
munmap(undos, round_page(data->size));
undos = NULL;
}
}
struct shm_data *
get_shmdata(int id, int to_remove, int shm_access)
{
struct shm_data *data = NULL;
SYSV_MUTEX_LOCK(&lock_resources);
if (!is_shm_started()) {
errno = EINVAL;
goto done;
}
data = _hash_lookup(shmres, id);
if (!data) {
errno = EINVAL;
goto done;
}
if (data->removed) {
sysv_print("segment already removed\n");
errno = EINVAL;
data = NULL;
goto done;
}
if (to_remove) {
sysv_print("segment is removed\n");
data->removed = to_remove;
goto done2;
}
if (!(data->access & shm_access)) {
#if 0
sysv_print("no access rights has %o and wants %o\n",
data->access, shm_access);
errno = EACCES;
data = NULL;
goto done;
#endif
}
done2:
data->used++;
done:
SYSV_MUTEX_UNLOCK(&lock_resources);
return (data);
}
int
set_shmdata_access(int id, int shm_access)
{
struct shm_data *data;
int ret = -1;
SYSV_MUTEX_LOCK(&lock_resources);
if (!is_shm_started()) {
errno = EINVAL;
goto done;
}
data = _hash_lookup(shmres, id);
if (!data) {
errno = EINVAL;
goto done;
}
if (data->removed) {
errno = EINVAL;
goto done;
}
data->access = shm_access;
ret = 0;
done:
SYSV_MUTEX_UNLOCK(&lock_resources);
return (ret);
}