#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/vmmeter.h>
#include <sys/wait.h>
#include <sys/queue.h>
#include <sys/mman.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include "limits.h"
#include "perm.h"
#include "utilsd.h"
#include "shmd.h"
#include "sysvipc_hash.h"
#include "sysvipc_sockets.h"
static struct shminfo shminfo = {
SHMMIN,
SHMMNI,
SHMSEG
};
static int shm_last_free, shm_committed, shmalloced;
int shm_nused;
static struct shmid_ds *shmsegs;
extern struct msginfo msginfo;
extern struct hashtable *clientshash;
static int
create_sysv_file(struct shmget_msg *msg, size_t size,
struct shmid_ds *shmseg) {
char filename[FILENAME_MAX];
int fd;
void *addr;
int nsems;
struct semid_pool *sems;
struct msqid_pool *msgq;
key_t key = msg->key;
int i;
errno = 0;
switch(msg->type) {
case SHMGET:
sprintf(filename, "%s/%s_%ld", DIRPATH, SHM_NAME, key);
break;
case SEMGET:
sprintf(filename, "%s/%s_%ld", DIRPATH, SEM_NAME, key);
break;
case MSGGET:
sprintf(filename, "%s/%s_%ld", DIRPATH, MSG_NAME, key);
break;
case UNDOGET:
sprintf(filename, "%s/%s_%ld", DIRPATH, UNDO_NAME, key);
break;
default:
return (-EINVAL);
}
fd = open(filename, O_RDWR | O_CREAT, 0666);
if (fd < 0) {
sysvd_print_err("create sysv file: open\n");
goto out;
}
ftruncate(fd, size);
switch(msg->type) {
case SEMGET:
addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (!addr) {
sysvd_print_err("create sysv file: mmap");
goto error;
}
sems = (struct semid_pool*)addr;
nsems = (msg->size - sizeof(struct semid_pool)) /
sizeof(struct sem);
sysvd_print("allocate %d sems\n", nsems);
#ifdef SYSV_RWLOCK
sysv_rwlock_init(&sems->rwlock);
#else
sysv_mutex_init(&sems->mutex);
#endif
sems->ds.sem_perm.seq = shmseg->shm_perm.seq;
sems->ds.sem_nsems = nsems;
sems->ds.sem_otime = 0;
sems->gen = 0;
memset(sems->ds.sem_base, 0, nsems + sizeof(struct sem));
#ifdef SYSV_SEMS
int l;
for (l=0; l < nsems; l++)
sysv_mutex_init(&sems->ds.sem_base[l].sem_mutex);
#endif
munmap(addr, size);
break;
case MSGGET:
addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (!addr) {
sysvd_print_err("create sysv file: mmap");
goto error;
}
msgq = (struct msqid_pool*)addr;
#ifdef SYSV_RWLOCK
sysv_rwlock_init(&msgq->rwlock);
#else
sysv_mutex_init(&msgq->mutex);
#endif
for (i = 0; i < msginfo.msgseg; i++) {
if (i > 0)
msgq->msgmaps[i-1].next = i;
msgq->msgmaps[i].next = -1;
}
msgq->free_msgmaps = 0;
msgq->nfree_msgmaps = msginfo.msgseg;
for (i = 0; i < msginfo.msgtql; i++) {
msgq->msghdrs[i].msg_type = 0;
if (i > 0)
msgq->msghdrs[i-1].msg_next = i;
msgq->msghdrs[i].msg_next = -1;
}
msgq->free_msghdrs = 0;
msgq->ds.msg_perm.seq = shmseg->shm_perm.seq;
msgq->ds.first.msg_first_index = -1;
msgq->ds.last.msg_last_index = -1;
msgq->ds.msg_cbytes = 0;
msgq->ds.msg_qnum = 0;
msgq->ds.msg_qbytes = msginfo.msgmnb;
msgq->ds.msg_lspid = 0;
msgq->ds.msg_lrpid = 0;
msgq->ds.msg_stime = 0;
msgq->ds.msg_rtime = 0;
munmap(addr, size);
break;
default:
break;
}
unlink(filename);
out:
return (fd);
error:
close(fd);
return (-1);
}
static int
install_fd_client(pid_t pid, int fd) {
int ret;
struct client *cl = _hash_lookup(clientshash, pid);
if (!cl) {
sysvd_print_err("no client entry for pid = %d\n", pid);
return (-1);
}
ret = send_fd(cl->sock, fd);
if (ret < 0) {
sysvd_print_err("can not send fd to client %d\n", pid);
return (-1);
}
return (0);
}
static int
shm_find_segment_by_key(key_t key)
{
int i;
for (i = 0; i < shmalloced; i++) {
if ((shmsegs[i].shm_perm.mode & SHMSEG_ALLOCATED) &&
shmsegs[i].shm_perm.key == key)
return (i);
}
return (-1);
}
static struct shmid_ds *
shm_find_segment_by_shmid(int shmid)
{
int segnum;
struct shmid_ds *shmseg;
segnum = IPCID_TO_IX(shmid);
if (segnum < 0 || segnum >= shmalloced) {
sysvd_print_err("segnum out of range\n");
return (NULL);
}
shmseg = &shmsegs[segnum];
if ((shmseg->shm_perm.mode & (SHMSEG_ALLOCATED | SHMSEG_REMOVED))
!= SHMSEG_ALLOCATED ||
shmseg->shm_perm.seq != IPCID_TO_SEQ(shmid)) {
sysvd_print("segment most probably removed\n");
return (NULL);
}
return (shmseg);
}
static void
shm_deallocate_segment(int segnum)
{
size_t size;
struct shmid_ds *shmseg = &shmsegs[segnum];
struct shm_handle *internal =
(struct shm_handle *)shmseg->shm_internal;
sysvd_print("deallocate segment %d\n", segnum);
size = round_page(shmseg->shm_segsz);
#if 0
if (internal->type == SEMGET) {
nsems = (shmseg->shm_segsz - sizeof(struct semid_pool)) /
sizeof(struct sem);
semtot -= nsems;
sysvd_print("freed %d sems\n", nsems);
}
#endif
close(internal->fd);
free(shmseg->shm_internal);
shmseg->shm_internal = NULL;
shm_committed -= btoc(size);
shm_nused--;
shmseg->shm_perm.mode = SHMSEG_FREE;
}
static void *map_seg(int);
static int munmap_seg(int, void *);
static void
mark_segment_removed(int shmid, int type) {
struct semid_pool *semaptr;
struct msqid_pool *msgq;
switch (type) {
case SEMGET:
semaptr = (struct semid_pool *)map_seg(shmid);
#ifdef SYSV_RWLOCK
sysv_rwlock_wrlock(&semaptr->rwlock);
#else
sysv_mutex_lock(&semaptr->mutex);
#endif
semaptr->gen = -1;
#ifdef SYSV_RWLOCK
sysv_rwlock_unlock(&semaptr->rwlock);
#else
sysv_mutex_unlock(&semaptr->mutex);
#endif
munmap_seg(shmid, semaptr);
break;
case MSGGET :
msgq = (struct msqid_pool*)map_seg(shmid);
#ifdef SYSV_RWLOCK
sysv_rwlock_wrlock(&msgq->rwlock);
#else
sysv_mutex_lock(&msgq->mutex);
#endif
msgq->gen = -1;
#ifdef SYSV_RWLOCK
sysv_rwlock_unlock(&msgq->rwlock);
#else
sysv_mutex_unlock(&msgq->mutex);
#endif
munmap_seg(shmid, msgq);
break;
default:
break;
}
}
static int
shmget_existing(struct shmget_msg *shmget_msg, int mode,
int segnum, struct cmsgcred *cred)
{
struct shmid_ds *shmseg;
int error;
shmseg = &shmsegs[segnum];
if (shmseg->shm_perm.mode & SHMSEG_REMOVED) {
}
if ((shmget_msg->shmflg & (IPC_CREAT | IPC_EXCL)) == (IPC_CREAT | IPC_EXCL))
return (-EEXIST);
error = ipcperm(cred, &shmseg->shm_perm, mode);
if (error)
return (-error);
if (shmget_msg->size && (shmget_msg->size > shmseg->shm_segsz))
return (-EINVAL);
return (IXSEQ_TO_IPCID(segnum, shmseg->shm_perm));
}
static int
shmget_allocate_segment(pid_t pid, struct shmget_msg *shmget_msg,
int mode, struct cmsgcred *cred)
{
int i, segnum, shmid;
size_t size;
struct shmid_ds *shmseg;
struct shm_handle *handle;
#if 0
if (shmget_msg->type == UNDOGET) {
struct client *cl= _hash_lookup(clientshash, pid);
if (cl->undoid != -1)
return cl->undoid;
}
#endif
if ((long)shmget_msg->size < shminfo.shmmin)
return (-EINVAL);
if (shm_nused >= shminfo.shmmni)
return (-ENOSPC);
size = round_page(shmget_msg->size);
if (shm_last_free < 0) {
for (i = 0; i < shmalloced; i++) {
if (shmsegs[i].shm_perm.mode & SHMSEG_FREE)
break;
}
if (i == shmalloced) {
sysvd_print("i == shmalloced\n");
return (-ENOSPC);
}
segnum = i;
} else {
segnum = shm_last_free;
shm_last_free = -1;
}
shmseg = &shmsegs[segnum];
shmseg->shm_perm.mode = SHMSEG_ALLOCATED | SHMSEG_REMOVED;
shmseg->shm_perm.key = shmget_msg->key;
shmseg->shm_perm.seq = (shmseg->shm_perm.seq + 1) & 0x7fff;
handle = shmseg->shm_internal = malloc(sizeof(struct shm_handle));
handle->type = shmget_msg->type;
handle->fd = create_sysv_file(shmget_msg, size, shmseg);
if (handle->fd == -1) {
free(handle);
handle = NULL;
shmseg->shm_perm.mode = SHMSEG_FREE;
shm_last_free = segnum;
errno = -ENFILE;
return (-1);
}
LIST_INIT(&handle->attached_list);
if (handle->fd < 0) {
free(shmseg->shm_internal);
shmseg->shm_internal = NULL;
shm_last_free = segnum;
shmseg->shm_perm.mode = SHMSEG_FREE;
return (-errno);
}
shmid = IXSEQ_TO_IPCID(segnum, shmseg->shm_perm);
shmseg->shm_perm.cuid = shmseg->shm_perm.uid = cred->cmcred_euid;
shmseg->shm_perm.cgid = shmseg->shm_perm.gid = cred->cmcred_gid;
shmseg->shm_perm.mode = (shmseg->shm_perm.mode & SHMSEG_WANTED) |
(mode & ACCESSPERMS) | SHMSEG_ALLOCATED;
shmseg->shm_cpid = pid;
shmseg->shm_lpid = shmseg->shm_nattch = 0;
shmseg->shm_atime = shmseg->shm_dtime = 0;
shmseg->shm_ctime = time(NULL);
shmseg->shm_segsz = shmget_msg->size;
shm_committed += btoc(size);
shm_nused++;
if (shmseg->shm_perm.mode & SHMSEG_WANTED) {
shmseg->shm_perm.mode &= ~SHMSEG_WANTED;
}
shmseg->shm_perm.mode &= ~SHMSEG_REMOVED;
if (shmget_msg->type == UNDOGET) {
struct client *cl= _hash_lookup(clientshash, pid);
cl->undoid = shmid;
}
return (shmid);
}
int
handle_shmget(pid_t pid, struct shmget_msg *shmget_msg,
struct cmsgcred *cred ) {
int segnum, mode, error;
struct shmid_ds *shmseg;
struct shm_handle *handle;
mode = shmget_msg->shmflg & ACCESSPERMS;
sysvd_print("ask for key = %ld\n", shmget_msg->key);
shmget_msg->key = (shmget_msg->key & 0x3FFF) |
(shmget_msg->type << 30);
sysvd_print("ask for key = %ld\n", shmget_msg->key);
if (shmget_msg->key != IPC_PRIVATE) {
segnum = shm_find_segment_by_key(shmget_msg->key);
if (segnum >= 0) {
error = shmget_existing(shmget_msg, mode, segnum, cred);
goto done;
}
if ((shmget_msg->shmflg & IPC_CREAT) == 0) {
error = -ENOENT;
goto done_err;
}
}
error = shmget_allocate_segment(pid, shmget_msg, mode, cred);
sysvd_print("allocate segment = %d\n", error);
done:
shmseg = shm_find_segment_by_shmid(error);
if (shmseg == NULL) {
sysvd_print_err("can not find segment by shmid\n");
return (-1);
}
handle = (struct shm_handle *)shmseg->shm_internal;
if (install_fd_client(pid, handle->fd) != 0)
error = errno;
done_err:
return (error);
}
int
handle_shmat(pid_t pid, struct shmat_msg *shmat_msg,
struct cmsgcred *cred ) {
int error;
int fd;
struct shmid_ds *shmseg;
struct pid_attached *pidatt;
struct shm_handle *handle;
size_t new_size = shmat_msg->size;
struct client *cl;
struct id_attached *idatt;
shmseg = shm_find_segment_by_shmid(shmat_msg->shmid);
if (shmseg == NULL) {
sysvd_print_err("shmat error: segment was not found\n");
error = EINVAL;
goto done;
}
error = ipcperm(cred, &shmseg->shm_perm,
(shmat_msg->shmflg & SHM_RDONLY) ? IPC_R : IPC_R|IPC_W);
if (error)
goto done;
handle = shmseg->shm_internal;
if (shmat_msg->size > shmseg->shm_segsz) {
if (handle->type != UNDOGET) {
error = EINVAL;
goto done;
}
fd = ((struct shm_handle*)shmseg->shm_internal)->fd;
ftruncate(fd, round_page(new_size));
shmseg->shm_segsz = new_size;
}
shmseg->shm_lpid = pid;
shmseg->shm_atime = time(NULL);
if (handle->type != UNDOGET)
shmseg->shm_nattch++;
else
shmseg->shm_nattch = 1;
sysvd_print("nattch = %d pid = %d\n",
shmseg->shm_nattch, pid);
pidatt = malloc(sizeof(*pidatt));
pidatt->pid = pid;
LIST_INSERT_HEAD(&handle->attached_list, pidatt, link);
idatt = malloc(sizeof(*idatt));
idatt->shmid = shmat_msg->shmid;
cl = _hash_lookup(clientshash, pid);
LIST_INSERT_HEAD(&cl->ids_attached, idatt, link);
return (0);
done:
return (error);
}
int
handle_shmdt(pid_t pid, int shmid) {
struct shmid_ds *shmseg;
int segnum;
struct shm_handle *handle;
struct pid_attached *pidatt;
struct id_attached *idatt;
struct client *cl;
sysvd_print("shmdt pid %d shmid %d\n", pid, shmid);
segnum = IPCID_TO_IX(shmid);
shmseg = &shmsegs[segnum];
handle = shmseg->shm_internal;
LIST_FOREACH(pidatt, &handle->attached_list, link)
if (pidatt->pid == pid)
break;
if (!pidatt) {
sysvd_print_err("process %d is not attached to %d (1)\n",
pid, shmid);
return (EINVAL);
}
LIST_REMOVE(pidatt, link);
cl = _hash_lookup(clientshash, pid);
LIST_FOREACH(idatt, &cl->ids_attached, link)
if (idatt->shmid == shmid)
break;
if (!idatt) {
sysvd_print_err("process %d is not attached to %d (2)\n",
pid, shmid);
return (EINVAL);
}
LIST_REMOVE(idatt, link);
shmseg->shm_dtime = time(NULL);
if ((--shmseg->shm_nattch <= 0) &&
(shmseg->shm_perm.mode & SHMSEG_REMOVED)) {
shm_deallocate_segment(segnum);
shm_last_free = segnum;
}
return (0);
}
int
handle_shmctl(struct shmctl_msg *shmctl_msg,
struct cmsgcred *cred ) {
int error = 0;
struct shmid_ds *shmseg, *inbuf;
shmseg = shm_find_segment_by_shmid(shmctl_msg->shmid);
if (shmseg == NULL) {
error = EINVAL;
goto done;
}
switch (shmctl_msg->cmd) {
case IPC_STAT:
sysvd_print("IPC STAT\n");
error = ipcperm(cred, &shmseg->shm_perm, IPC_R);
if (error) {
sysvd_print("IPC_STAT not allowed\n");
break;
}
shmctl_msg->buf = *shmseg;
break;
case IPC_SET:
sysvd_print("IPC SET\n");
error = ipcperm(cred, &shmseg->shm_perm, IPC_M);
if (error) {
sysvd_print("IPC_SET not allowed\n");
break;
}
inbuf = &shmctl_msg->buf;
shmseg->shm_perm.uid = inbuf->shm_perm.uid;
shmseg->shm_perm.gid = inbuf->shm_perm.gid;
shmseg->shm_perm.mode =
(shmseg->shm_perm.mode & ~ACCESSPERMS) |
(inbuf->shm_perm.mode & ACCESSPERMS);
shmseg->shm_ctime = time(NULL);
break;
case IPC_RMID:
sysvd_print("IPC RMID shmid = %d\n",
shmctl_msg->shmid);
error = ipcperm(cred, &shmseg->shm_perm, IPC_M);
if (error) {
sysvd_print("IPC_RMID not allowed\n");
break;
}
shmseg->shm_perm.key = IPC_PRIVATE;
shmseg->shm_perm.mode |= SHMSEG_REMOVED;
if (shmseg->shm_nattch <= 0) {
shm_deallocate_segment(IPCID_TO_IX(shmctl_msg->shmid));
shm_last_free = IPCID_TO_IX(shmctl_msg->shmid);
}
else {
struct shm_handle *internal =
(struct shm_handle *)shmseg->shm_internal;
mark_segment_removed(shmctl_msg->shmid,
internal->type);
}
break;
#if 0
case SHM_LOCK:
case SHM_UNLOCK:
#endif
default:
error = EINVAL;
break;
}
done:
return (error);
}
static void *
map_seg(int shmid) {
struct shmid_ds *shmseg;
struct shm_handle *internal;
int fd;
size_t size;
void *addr;
shmseg = shm_find_segment_by_shmid(shmid);
if (!shmseg) {
sysvd_print_err("map_seg error:"
"semid %d not found\n", shmid);
return (NULL);
}
internal = (struct shm_handle *)shmseg->shm_internal;
if (!internal) {
sysvd_print_err("map_seg error: internal for"
"semid %d not found\n", shmid);
return (NULL);
}
fd = internal->fd;
size = round_page(shmseg->shm_segsz);
addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (!addr) {
sysvd_print_err("map_seg: error mmap semid = %d\n", shmid);
return (NULL);
}
return (addr);
}
static int
munmap_seg(int shmid, void *addr) {
struct shmid_ds *shmseg;
struct shm_handle *internal;
size_t size;
shmseg = shm_find_segment_by_shmid(shmid);
if (!shmseg) {
sysvd_print_err("munmap_seg error:"
"semid %d not found\n", shmid);
return (-1);
}
internal = (struct shm_handle *)shmseg->shm_internal;
if (!internal) {
sysvd_print_err("munmap_seg error: internal for"
"semid %d not found\n", shmid);
return (-1);
}
size = round_page(shmseg->shm_segsz);
munmap(addr, size);
return (0);
}
void
shminit(void) {
int i;
shmalloced = shminfo.shmmni;
shmsegs = malloc(shmalloced * sizeof(shmsegs[0]));
for (i = 0; i < shmalloced; i++) {
shmsegs[i].shm_perm.mode = SHMSEG_FREE;
shmsegs[i].shm_perm.seq = 0;
}
shm_last_free = 0;
shm_nused = 0;
shm_committed = 0;
i = 8;
while (i < 1024 && i != msginfo.msgssz)
i <<= 1;
if (i != msginfo.msgssz) {
sysvd_print_err("msginfo.msgssz=%d (0x%x)\n", msginfo.msgssz,
msginfo.msgssz);
sysvd_print_err("msginfo.msgssz not a small power of 2");
exit(-1);
}
msginfo.msgmax = msginfo.msgseg * msginfo.msgssz;
}
int
semexit(int undoid) {
struct sem_undo *suptr;
struct sem *semptr;
struct shmid_ds *undoseg;
if (undoid < 0) {
return (-1);
}
undoseg = shm_find_segment_by_shmid(undoid);
if (undoseg->shm_nattch != 1) {
sysvd_print_err("undo segment mapped by more"
"than one process\n");
exit(-1);
}
suptr = (struct sem_undo *)map_seg(undoid);
if (suptr == NULL) {
sysvd_print_err("no %d undo segment found\n", undoid);
return (-1);
}
while (suptr->un_cnt) {
struct semid_pool *semaptr;
int semid;
int semnum;
int adjval;
int ix;
ix = suptr->un_cnt - 1;
semid = suptr->un_ent[ix].un_id;
semnum = suptr->un_ent[ix].un_num;
adjval = suptr->un_ent[ix].un_adjval;
semaptr = (struct semid_pool *)map_seg(semid);
if (!semaptr) {
return (-1);
}
if (semaptr->gen == -1 ||
semaptr->ds.sem_perm.seq != IPCID_TO_SEQ(semid) ||
(semaptr->ds.sem_perm.mode & SHMSEG_ALLOCATED) == 0) {
--suptr->un_cnt;
sysvd_print_err("semexit - semid not allocated\n");
continue;
}
if (semnum >= semaptr->ds.sem_nsems) {
--suptr->un_cnt;
sysvd_print_err("semexit - semnum out of range\n");
continue;
}
#ifdef SYSV_RWLOCK
#ifdef SYSV_SEMS
sysv_rwlock_rdlock(&semaptr->rwlock);
#else
sysv_rwlock_wrlock(&semaptr->rwlock);
#endif
#else
sysv_mutex_lock(&semaptr->mutex);
#endif
semptr = &semaptr->ds.sem_base[semnum];
#ifdef SYSV_SEMS
sysv_mutex_lock(&semptr->sem_mutex);
#endif
if (ix == suptr->un_cnt - 1 &&
semid == suptr->un_ent[ix].un_id &&
semnum == suptr->un_ent[ix].un_num &&
adjval == suptr->un_ent[ix].un_adjval) {
--suptr->un_cnt;
if (adjval < 0) {
if (semptr->semval < -adjval)
semptr->semval = 0;
else
semptr->semval += adjval;
} else {
semptr->semval += adjval;
}
umtx_wakeup((int *)&semptr->semval, 0);
}
#ifdef SYSV_SEMS
sysv_mutex_unlock(&semptr->sem_mutex);
#endif
#ifdef SYSV_RWLOCK
sysv_rwlock_unlock(&semaptr->rwlock);
#else
sysv_mutex_unlock(&semaptr->mutex);
#endif
munmap_seg(semid, semaptr);
}
munmap_seg(undoid, suptr);
return (0);
}
void
shmexit(struct client *cl) {
struct id_attached *idatt;
while (!LIST_EMPTY(&cl->ids_attached)) {
idatt = LIST_FIRST(&cl->ids_attached);
handle_shmdt(cl->pid, idatt->shmid);
}
}