/* $Id$ */
/*
* Copyright (c) 2006 Nicholas Marriott <nicholas.marriott@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include "fdm.h"
/*
* This implements shared memory using mmap'd files in TMPDIR.
*/
int shm_expand(struct shm *, size_t);
char shm_block[BUFSIZ];
#ifdef MAP_NOSYNC
#define SHM_FLAGS MAP_SHARED|MAP_NOSYNC
#else
#define SHM_FLAGS MAP_SHARED
#endif
#define SHM_PROT PROT_READ|PROT_WRITE
/* Work out shm path. */
char *
shm_path(struct shm *shm)
{
static char path[MAXPATHLEN];
if (ppath(path, sizeof path, "%s/%s", conf.tmp_dir, shm->name) != 0)
return (NULL);
return (path);
}
/* Expand or reduce shm file to size. */
int
shm_expand(struct shm *shm, size_t size)
{
ssize_t n;
if (size == shm->size)
return (0);
if (size < shm->size)
return (ftruncate(shm->fd, size) != 0);
if (lseek(shm->fd, shm->size, SEEK_SET) == -1)
return (-1);
/*
* Fill the file using write(2) to avoid fragmentation problems on
* FreeBSD and also to detect disk full.
*/
while (size > sizeof shm_block) {
if ((n = write(shm->fd, shm_block, sizeof shm_block)) == -1)
return (-1);
if (n != sizeof shm_block) {
errno = EIO;
return (-1);
}
size -= sizeof shm_block;
}
if (size > 0) {
if ((n = write(shm->fd, shm_block, size)) == -1)
return (-1);
if ((size_t) n != size) {
errno = EIO;
return (-1);
}
}
/*
* Sync the fd, should hopefully fail if disk full.
*/
if (fsync(shm->fd) != 0)
return (-1);
return (0);
}
/* Create an shm file and map it. */
void *
shm_create(struct shm *shm, size_t size)
{
int saved_errno;
char *path;
if (size == 0)
fatalx("zero size");
if (ppath(
shm->name, sizeof shm->name, "%s.XXXXXXXXXX", __progname) != 0)
return (NULL);
if ((path = shm_path(shm)) == NULL)
return (NULL);
if ((shm->fd = mkstemp(path)) == -1)
return (NULL);
strlcpy(shm->name, xbasename(path), sizeof shm->name);
if (shm_expand(shm, size) != 0)
goto error;
shm->data = mmap(NULL, size, SHM_PROT, SHM_FLAGS, shm->fd, 0);
if (shm->data == MAP_FAILED)
goto error;
madvise(shm->data, size, MADV_SEQUENTIAL);
shm->size = size;
return (shm->data);
error:
saved_errno = errno;
unlink(path);
errno = saved_errno;
return (NULL);
}
/* Destroy shm file. */
void
shm_destroy(struct shm *shm)
{
char *path;
if (*shm->name == '\0')
return;
shm_close(shm);
if ((path = shm_path(shm)) == NULL)
fatal("unlink failed");
if (unlink(path) != 0)
fatal("unlink failed");
*shm->name = '\0';
}
/* Close and unmap shm without destroying file. */
void
shm_close(struct shm *shm)
{
if (shm->fd == -1)
return;
if (munmap(shm->data, shm->size) != 0)
fatal("munmap failed");
shm->data = NULL;
close(shm->fd);
shm->fd = -1;
}
/* Reopen and map shm file. */
void *
shm_reopen(struct shm *shm)
{
char *path;
if ((path = shm_path(shm)) == NULL)
return (NULL);
if ((shm->fd = open(path, O_RDWR, 0)) == -1)
return (NULL);
shm->data = mmap(NULL, shm->size, SHM_PROT, SHM_FLAGS, shm->fd, 0);
if (shm->data == MAP_FAILED)
return (NULL);
madvise(shm->data, shm->size, MADV_SEQUENTIAL);
return (shm->data);
}
/* Set ownership of shm file. */
int
shm_owner(struct shm *shm, uid_t uid, gid_t gid)
{
if (fchown(shm->fd, uid, gid) != 0)
return (-1);
return (0);
}
/* Resize an shm file. */
void *
shm_resize(struct shm *shm, size_t nmemb, size_t size)
{
size_t newsize = nmemb * size;
if (size == 0)
fatalx("zero size");
if (SIZE_MAX / nmemb < size)
fatalx("nmemb * size > SIZE_MAX");
#ifndef HAVE_MREMAP
if (munmap(shm->data, shm->size) != 0)
fatal("munmap failed");
shm->data = NULL;
#endif
if (shm_expand(shm, newsize) != 0)
return (NULL);
#ifdef HAVE_MREMAP
shm->data = mremap(shm->data, shm->size, newsize, MREMAP_MAYMOVE);
#else
shm->data = mmap(NULL, newsize, SHM_PROT, SHM_FLAGS, shm->fd, 0);
#endif
if (shm->data == MAP_FAILED)
return (NULL);
madvise(shm->data, newsize, MADV_SEQUENTIAL);
shm->size = newsize;
return (shm->data);
}