[ccan] [PATCH] rszshm: New module

Dan Good dan at dancancode.com
Tue Jan 19 17:04:02 AEDT 2016


rszshm - resizable pointer-safe shared memory

Signed-off-by: Dan Good <dan at dancancode.com>
---
 Makefile-ccan          |   1 +
 ccan/rszshm/LICENSE    |   1 +
 ccan/rszshm/_info      |  96 ++++++++++++++++
 ccan/rszshm/rszshm.c   | 240 +++++++++++++++++++++++++++++++++++++++
 ccan/rszshm/rszshm.h   | 301 +++++++++++++++++++++++++++++++++++++++++++++++++
 ccan/rszshm/test/run.c | 194 +++++++++++++++++++++++++++++++
 6 files changed, 833 insertions(+)
 create mode 120000 ccan/rszshm/LICENSE
 create mode 100644 ccan/rszshm/_info
 create mode 100644 ccan/rszshm/rszshm.c
 create mode 100644 ccan/rszshm/rszshm.h
 create mode 100644 ccan/rszshm/test/run.c

diff --git a/Makefile-ccan b/Makefile-ccan
index 8d99bbf..9469334 100644
--- a/Makefile-ccan
+++ b/Makefile-ccan
@@ -100,6 +100,7 @@ MODS_WITH_SRC := aga \
 	rbuf \
 	read_write_all \
 	rfc822 \
+	rszshm \
 	siphash \
 	sparse_bsearch \
 	str \
diff --git a/ccan/rszshm/LICENSE b/ccan/rszshm/LICENSE
new file mode 120000
index 0000000..4f8ee74
--- /dev/null
+++ b/ccan/rszshm/LICENSE
@@ -0,0 +1 @@
+../../licenses/APACHE-2
\ No newline at end of file
diff --git a/ccan/rszshm/_info b/ccan/rszshm/_info
new file mode 100644
index 0000000..2134e67
--- /dev/null
+++ b/ccan/rszshm/_info
@@ -0,0 +1,96 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * rszshm - resizable pointer-safe shared memory
+ *
+ * If two separate processes have shared mappings of the same file at the
+ * same address, then pointers to addresses within the region can be shared
+ * between the processes and safely dereferenced.
+ *
+ * Mapping to the same address in unrelated processes is nontrivial.  One can
+ * request a specific address, but mmap will return another in case of an
+ * overlap.  One can require a specific address, but mmap will unmap anything
+ * overlapped.  On Linux boxes it can be seen that the used addresses clump
+ * at either end of the address range.  rszshm tries to mmap in the middle
+ * of the address range, and checks if the requested address matches the
+ * returned address.  If not, additional addresses are tried.  Once mapped,
+ * the address is recorded to a header in the file.  Another process reads the
+ * header and requests the saved address from mmap.  If the returned address
+ * matches, work proceeds.  While the defaults provide a propitious search,
+ * all the search parameters may be specified.
+ *
+ * To accommodate resizing, rszshm first maps a large, private, noreserve map.
+ * This serves to claim a span of addresses.  The shared file mapping then
+ * overlays the beginning of the span.  Later calls to extend the mapping
+ * overlay more of the span.  Attempts to extend beyond the end of the span
+ * return an error.
+ *
+ * Example:
+ * 	// fork x times, grow and fill shared memory cooperatively
+ * 	#include <assert.h>
+ * 	#include <err.h>
+ * 	#include <signal.h>
+ * 	#include <stdio.h>
+ * 	#include <unistd.h>
+ * 	#include <ccan/rszshm/rszshm.h>
+ *
+ * 	#define ok(x) ({ int n = (x); if (n == -1) err(1, "%s", #x); n; })
+ *
+ * 	int main(int argc, char *argv[]) {
+ * 		int pidcnt, stopval, n, i;
+ * 		struct rszshm *r;
+ * 		char *m;
+ *
+ * 		assert(argc == 3);
+ * 		pidcnt  = atoi(argv[1]);
+ * 		stopval = atoi(argv[2]);
+ * 		assert(pidcnt > 0 && stopval > 0);
+ *
+ * 		if (!rszshm_mkm(r, 4096, NULL))
+ * 			err(1, "rszshm_mkm");
+ *
+ * 		printf("%s\n", r->fname);
+ *
+ * 		for (n = 0; n < pidcnt - 1; n++)
+ * 			if (ok(fork()) == 0)
+ * 				break;
+ *
+ * 		m = (char *) r->dat + sizeof(int);
+ * 		#define next() (__sync_fetch_and_add((int *) r->dat, 1))
+ *
+ * 		for(i = next(); i < stopval; i = next()) {
+ * 			if (i >= r->cap - sizeof(int))
+ * 				ok(rszshm_grow(r));
+ * 			assert(m[i] == '\0');
+ * 			m[i] = 'A' + n;
+ * 			kill(0, 0); // busy work
+ * 		}
+ *
+ * 		rszshm_free(r);
+ * 		return 0;
+ * 	}
+ * 	// $ ./foo 8 $((4*1024*1024-28))
+ * 	// /dev/shm/rszshm_LAsEvt/0
+ * 	// $ tail -c +29 /dev/shm/rszshm_LAsEvt/0 | sed 's/./&\n/g' | sort | uniq -c | tr '\n' '\t'; echo
+ * 	//  515532 A   527251 B    512930 C    513062 D    544326 E    545876 F    512936 G    522363 H
+ *
+ * License: APACHE-2
+ * Author: Dan Good <dan at dancancode.com>
+ *
+ * Ccanlint:
+ *	// tests use optional macros containing statement expressions
+ *	tests_compile_without_features FAIL
+ */
+int main(int argc, char *argv[])
+{
+	/* Expect exactly one argument */
+	if (argc != 2)
+		return 1;
+
+	if (strcmp(argv[1], "depends") == 0)
+		return 0;
+
+	return 1;
+}
diff --git a/ccan/rszshm/rszshm.c b/ccan/rszshm/rszshm.c
new file mode 100644
index 0000000..32db455
--- /dev/null
+++ b/ccan/rszshm/rszshm.c
@@ -0,0 +1,240 @@
+/* Licensed under Apache License v2.0 - see LICENSE file for details */
+#include "config.h"
+#include "rszshm.h"
+
+#define _XOPEN_SOURCE 700
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/file.h>
+
+#define pgup(x, pgsz) (((x) + (pgsz) - 1) & ~((pgsz) - 1))
+
+void *rszshm_mk(struct rszshm *r, size_t flen, const char *fname, struct rszshm_scan scan)
+{
+	long pgsz = sysconf(_SC_PAGE_SIZE);
+	int i, errno_;
+	char *m, *tgt, *p = NULL;
+
+	if (!r || flen == 0 || scan.len < flen + sizeof(*r->hdr) ||
+	    !scan.start || scan.len == 0 || scan.hop == 0 || scan.iter == 0 ||
+	    (fname && strnlen(fname, RSZSHM_PATH_MAX) == RSZSHM_PATH_MAX)) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	*r = (typeof(*r)) { -1, 0, "", NULL, NULL };
+	strcpy(r->fname, fname ? fname : RSZSHM_DFLT_FNAME);
+
+	flen = pgup(flen + sizeof(*r->hdr), pgsz);
+	scan.len = pgup(scan.len, pgsz);
+
+	for (i = 1, tgt = scan.start; i <= scan.iter; i++) {
+		m = mmap(tgt, scan.len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON|MAP_NORESERVE, -1, 0);
+		if (m == MAP_FAILED)
+			return NULL;
+		if (m == tgt)
+			break;
+		munmap(m, scan.len);
+		m = NULL;
+		tgt += (i % 2 == 0 ? 1 : -1) * i * scan.hop;
+	}
+	if (!m) {
+		errno = ENOSPC;
+		return NULL;
+	}
+
+	if ((p = strstr(r->fname, "XXXXXX/")) != NULL) {
+		p += 6;
+		*p = '\0';
+		if (!mkdtemp(r->fname))
+			goto err;
+		*p = '/';
+	}
+
+	if ((r->fd = open(r->fname, O_CREAT|O_EXCL|O_RDWR, p ? 0600 : 0666)) == -1)
+		goto err;
+
+	if (ftruncate(r->fd, flen) == -1)
+		goto err;
+
+	if (mmap(m, flen, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, r->fd, 0) == MAP_FAILED)
+		goto err;
+
+	*(r->hdr = (typeof(r->hdr)) m) = (typeof(*r->hdr)) { flen, scan.len, m };
+
+	if (msync(m, sizeof(*r->hdr), MS_SYNC) == -1)
+		goto err;
+
+	r->flen = flen;
+	r->cap = flen - sizeof(*r->hdr);
+	r->dat = m + sizeof(*r->hdr);
+
+	return r->dat;
+
+err:
+	errno_ = errno;
+	if (m && m != MAP_FAILED)
+		munmap(m, scan.len);
+	if (r->fd != -1) {
+		close(r->fd);
+		unlink(r->fname);
+	}
+	if (p) {
+		*p = '\0';
+		rmdir(r->fname);
+		*p = '/';
+	}
+	errno = errno_;
+	return NULL;
+}
+
+void *rszshm_at(struct rszshm *r, const char *fname)
+{
+	struct rszshm_hdr h;
+	int fd = -1, ret, errno_;
+	char *m = NULL;
+
+	if (!r || !fname || !fname[0] ||
+	    strnlen(fname, RSZSHM_PATH_MAX) == RSZSHM_PATH_MAX) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	if ((fd = open(fname, O_RDWR)) == -1)
+		return NULL;
+
+	if ((ret = read(fd, &h, sizeof(h))) == -1)
+		goto err;
+
+	if (ret != sizeof(h) || !h.addr || h.flen == 0 || h.max == 0) {
+		errno = ENODATA;
+		goto err;
+	}
+
+	m = mmap(h.addr, h.max, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON|MAP_NORESERVE, -1, 0);
+	if (m == MAP_FAILED)
+		goto err;
+	if (m != h.addr) {
+		errno = ENOSPC;
+		goto err;
+	}
+
+	if (mmap(m, h.flen, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, fd, 0) == MAP_FAILED)
+		goto err;
+
+	*r = (typeof(*r)) { .fd = fd, .flen = h.flen, .hdr = (typeof(r->hdr)) m,
+			    .dat = m + sizeof(h), .cap = h.flen - sizeof(h) };
+	strcpy(r->fname, fname);
+
+	return r->dat;
+
+err:
+	errno_ = errno;
+	if (m && m != MAP_FAILED)
+		munmap(m, h.max);
+	close(fd);
+	errno = errno_;
+	return NULL;
+}
+
+#undef rszshm_up
+int rszshm_up(struct rszshm *r)
+{
+	size_t flen;
+
+	assert(r);
+
+	flen = r->hdr->flen;
+	if (r->flen == flen)
+		return 0;
+	if (mmap(r->hdr, flen, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, r->fd, 0) == MAP_FAILED)
+		return -1;
+
+	r->flen = flen;
+	r->cap = flen - sizeof(*r->hdr);
+	return 1;
+}
+
+int rszshm_grow(struct rszshm *r)
+{
+	int ret;
+	assert(r);
+
+	if ((ret = rszshm_up(r)) != 0)
+		return ret;
+
+	if (r->flen == r->hdr->max) {
+		errno = ENOMEM;
+		return -1;
+	}
+
+	if (flock(r->fd, LOCK_EX) == -1)
+		return -1;
+
+	if ((ret = rszshm_up(r)) == 0) {
+		int flen = r->hdr->flen * 2 < r->hdr->max ? r->hdr->flen * 2 : r->hdr->max;
+
+		if (ftruncate(r->fd, flen) != -1 &&
+		    mmap(r->hdr, flen, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, r->fd, 0) != MAP_FAILED) {
+			r->flen = r->hdr->flen = flen;
+			r->cap = flen - sizeof(*r->hdr);
+			ret = 1;
+		}
+		else
+			ret = -1;
+	}
+
+	flock(r->fd, LOCK_UN);
+	return ret;
+}
+
+int rszshm_dt(struct rszshm *r)
+{
+	int ret[3];
+	assert(r);
+
+	/* ok to call twice, since free macro calls this */
+	if (r->fd == -1)
+		return 0;
+
+	ret[0] = msync(r->hdr, r->flen, MS_SYNC);
+	ret[1] = munmap(r->hdr, r->hdr->max);
+	ret[2] = close(r->fd);
+
+	r->fd = -1;
+	r->flen = 0;
+	r->hdr = NULL;
+	r->dat = NULL;
+	r->cap = 0;
+
+	return ret[0] == 0 && ret[1] == 0 && ret[2] == 0 ? 0 : -1;
+}
+
+int rszshm_unlink(struct rszshm *r)
+{
+	assert(r);
+	return unlink(r->fname);
+}
+
+int rszshm_rmdir(struct rszshm *r)
+{
+	int ret;
+	char *p;
+
+	assert(r);
+
+	if ((p = strrchr(r->fname, '/')) == NULL) {
+		errno = ENOTDIR;
+		return -1;
+	}
+
+	*p = '\0';
+	ret = rmdir(r->fname);
+	*p = '/';
+	return ret;
+}
diff --git a/ccan/rszshm/rszshm.h b/ccan/rszshm/rszshm.h
new file mode 100644
index 0000000..c29e4a5
--- /dev/null
+++ b/ccan/rszshm/rszshm.h
@@ -0,0 +1,301 @@
+/* Licensed under Apache License v2.0 - see LICENSE file for details */
+#ifndef CCAN_RSZSHM_H
+#define CCAN_RSZSHM_H
+#include "config.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/**
+ * struct rszshm_scan - parameters for the free region search
+ * @start: first address to test
+ * @len: size of region to test
+ * @hop: offset of the next test
+ * @iter: number of attempts
+ *
+ * See rszshm_mk for search details.
+ */
+struct rszshm_scan {
+	void *start;
+	size_t len;
+	size_t hop;
+	unsigned iter;
+};
+
+#define KiB (1024UL)
+#define MiB (KiB*KiB)
+#define GiB (MiB*KiB)
+#ifdef __x86_64__
+#define TiB (GiB*KiB)
+#define RSZSHM_DFLT_SCAN (struct rszshm_scan) { (void *) (64*TiB), 4*GiB, 1*TiB, 10 }
+#else
+#define RSZSHM_DFLT_SCAN (struct rszshm_scan) { (void *) ((1024+512)*MiB), 256*MiB, 256*MiB, 10 }
+#endif
+
+/**
+ * struct rszshm_hdr - header describing mapped memory
+ * @flen: length of the shared file mapping
+ * @max: length of the private mapping
+ * @addr: address of the mapping
+ *
+ * The shared region is mapped over the private region.
+ * max is the maximum size the shared region can be extended.
+ * addr and max are set at creation time and do not change.
+ * flen is updated each time the file and shared region is grown.
+ */
+struct rszshm_hdr {
+	size_t flen;
+	size_t max;
+	void *addr;
+};
+
+/**
+ * struct rszshm - handle for a mapped region
+ * @fd: file descriptor of the mapped file
+ * @flen: length of the mapped shared file in this process
+ * @fname: path of the mapped file
+ * @hdr: pointer to the mapped region header
+ * @dat: pointer to the usable space after the header
+ * @cap: length of the usable space after the header
+ *
+ * flen is updated by rszshm_grow, or by rszshm_up.
+ */
+#define RSZSHM_PATH_MAX 128
+#define RSZSHM_DFLT_FNAME "/dev/shm/rszshm_XXXXXX/0"
+struct rszshm {
+	int fd;
+	size_t flen;
+	char fname[RSZSHM_PATH_MAX];
+	struct rszshm_hdr *hdr;
+	void *dat;
+	size_t cap;
+};
+
+/**
+ * rszshm_mk - make and mmap a shareable region
+ * @r: pointer to handle
+ * @flen: initial length of shared mapping
+ * @fname: path to file to be created, may be NULL or contain template
+ * @scan: struct specifying search parameters
+ *
+ * The handle pointed to by r is populated by rszshm_mk. flen is increased
+ * by the size of struct rszshm_hdr and rounded up to the next multiple of
+ * page size. If the directory portion of fname ends with XXXXXX, mkdtemp(3)
+ * is used. If fname is NULL, a default path with template is used.
+ *
+ * If rszshm_mk is called with only three arguments, a default scan struct
+ * is used. To supply a struct via compound literal, wrap the argument in
+ * parenthesis to avoid macro failure.
+ *
+ * rszshm_mk attempts to mmap a region of scan.len size at scan.start address.
+ * This is a private anonymous noreserve map used to claim an address space.
+ * If the mapping returns a different address, the region is unmapped, and
+ * another attempt is made at scan.start - scan.hop. If necessary, the next
+ * address tried is scan.start + scan.hop, then scan.start - (2 * scan.hop),
+ * and so on for at most scan.iter iterations. The pattern can be visualized
+ * as a counterclockwise spiral. If no match is found, NULL is returned and
+ * errno is set to ENOSPC.
+ *
+ * When an mmap returns an address matching the requested address, that region
+ * is used. If fname contains a template, mkdtemp(3) is called. A file is
+ * created, and extended to flen bytes. It must not already exist. This file
+ * is mmap'd over the region using MAP_FIXED. The mapping may later be extended
+ * by rszshm_grow consuming more of the claimed address space.
+ *
+ * The initial portion of the mapped file is populated with a struct rszshm_hdr,
+ * and msync called to write out the header.
+ *
+ * Example:
+ *	struct rszshm r, s, t;
+ *
+ *	if (!rszshm_mk(&r, 4*MiB, NULL))
+ *		err(1, "rszshm_mk");
+ *	// map at 0x400000000000
+ *
+ *	if (!rszshm_mk(&s, 4*MiB, "/var/tmp/dat"))
+ *		err(1, "rszshm_mk");
+ *	// map at 0x3f0000000000
+ *
+ *	if (!rszshm_mk(&t, 4*MiB, NULL, ((struct rszshm_scan) { (void *) (48*TiB), 4*GiB, 1*TiB, 10 })))
+ *		err(1, "rszshm_mk");
+ *	// map at 0x300000000000
+ *
+ * Returns: r->dat address on success, NULL on error
+ */
+void *rszshm_mk(struct rszshm *r, size_t flen, const char *fname, struct rszshm_scan scan);
+#define __4args(a,b,c,d,...) a, b, c, d
+#define rszshm_mk(...) rszshm_mk(__4args(__VA_ARGS__, RSZSHM_DFLT_SCAN))
+
+#if HAVE_STATEMENT_EXPR
+/**
+ * rszshm_mkm - malloc handle and run rszshm_mk
+ * @r: pointer to handle
+ * @flen: initial length of shared mapping
+ * @fname: path to file to be created, may be NULL or contain template
+ *
+ * Example:
+ *	struct rszshm *r;
+ *
+ *	if (!rszshm_mkm(r, 4*MiB, NULL))
+ *		err(1, "rszshm_mkm");
+ *
+ * Returns: result of rszshm_mk
+ */
+#define rszshm_mkm(r, fl, fn) ({			\
+	void *__p = NULL;				\
+	r = malloc(sizeof(*r));				\
+	if (r && !(__p = rszshm_mk(r, fl, fn))) {	\
+		free(r);				\
+		r = 0;					\
+	}						\
+	__p;						\
+})
+#endif
+
+/**
+ * rszshm_at - mmap ("attach") an existing shared region
+ * @r: pointer to handle
+ * @fname: path to file
+ *
+ * rszshm_at lets unrelated processes attach an existing shared region.
+ * fname must name a file previously created by rszshm_mk in another process.
+ * Note, fork'd children of the creating process inherit the mapping and
+ * should *not* call rszshm_at.
+ *
+ * rszshm_at opens and reads the header from the file. It makes a private
+ * anonymous noreserve mapping at the address recorded in the header.
+ * If mmap returns an address other than the requested one, munmap
+ * is called, errno is set to ENOSPC, and NULL is returned.
+ *
+ * Once the address space is claimed, the file is mmap'd over the region
+ * using MAP_FIXED. The remaining claimed address space will be used by
+ * later calls to rszshm_grow. Finally, the handle is populated and r->dat
+ * returned.
+ *
+ * Example:
+ *	struct rszshm r;
+ *
+ *	if (!rszshm_at(&r, "/dev/shm/rszshm_LAsEvt/0"))
+ *		err(1, "rszshm_at");
+ *
+ * Returns: r->dat address on success, NULL on error
+ */
+void *rszshm_at(struct rszshm *r, const char *fname);
+
+#if HAVE_STATEMENT_EXPR
+/**
+ * rszshm_atm - malloc handle and run rszshm_at
+ * @r: pointer to handle
+ * @fname: path to file
+ *
+ * Example:
+ *	struct rszshm *r;
+ *
+ *	if (!rszshm_atm(r, "/dev/shm/rszshm_LAsEvt/0"))
+ *		err(1, "rszshm_atm");
+ *
+ * Returns: result of rszshm_at
+ */
+#define rszshm_atm(r, f) ({				\
+	void *__p = NULL;				\
+	r = malloc(sizeof(*r));				\
+	if (r && !(__p = rszshm_at(r, f))) {		\
+		free(r);				\
+		r = 0;					\
+	}						\
+	__p;						\
+})
+#endif
+
+/**
+ * rszshm_dt - unmap ("detach") shared region
+ * @r: pointer to handle
+ *
+ * Calls msync, munmap, and close. Resets handle values except fname.
+ * (fname is used by rszshm_rm*.)
+ *
+ * Returns: 0 on success, -1 if any call failed
+ */
+int rszshm_dt(struct rszshm *r);
+
+/**
+ * rszshm_up - update mapping of shared region
+ * @r: pointer to handle
+ *
+ * Check if flen from the region header matches flen from the handle.
+ * They will diverge when another process runs rszshm_grow.
+ * If they are different, call mmap with the header flen and MAP_FIXED,
+ * and update handle.
+ *
+ * Returns: -1 if mmap fails, 0 for no change, 1 is mapping updated
+ */
+int rszshm_up(struct rszshm *r);
+#define rszshm_up(r) (assert(r), (r)->flen == (r)->hdr->flen ? 0 : rszshm_up(r))
+
+/**
+ * rszshm_grow - double the shared region, conditionally
+ * @r: pointer to handle
+ *
+ * If the region is already at capacity, set errno to ENOMEM, and return -1.
+ *
+ * rszshm_up is called, to see if another process has already grown the region.
+ * If not, a lock is acquired and the check repeated, to avoid races.
+ * The file is extended, and mmap called with MAP_FIXED. The header and handle
+ * are updated.
+ *
+ * Returns: 1 on success, -1 on error
+ */
+int rszshm_grow(struct rszshm *r);
+
+/**
+ * rszshm_unlink - unlink shared file
+ * @r: pointer to handle
+ *
+ * Returns: result of unlink
+ */
+int rszshm_unlink(struct rszshm *r);
+
+/**
+ * rszshm_rmdir - rmdir of fname directory
+ * @r: pointer to handle
+ *
+ * Returns: result of rmdir
+ */
+int rszshm_rmdir(struct rszshm *r);
+
+#if HAVE_STATEMENT_EXPR
+/**
+ * rszshm_rm - remove file and directory
+ * @r: pointer to handle
+ *
+ * Calls rszshm_unlink and rszshm_rmdir.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+#define rszshm_rm(r) ({				\
+	int __ret;				\
+	assert(r);				\
+	__ret = rszshm_unlink(r);		\
+	if (__ret == 0)				\
+		__ret = rszshm_rmdir(r);	\
+	__ret;					\
+})
+#endif
+
+#if HAVE_STATEMENT_EXPR
+/**
+ * rszshm_free - run rszshm_dt and free malloced handle
+ * @r: pointer to handle
+ *
+ * Returns: result of rszshm_dt
+ */
+#define rszshm_free(r) ({	\
+	int __i = rszshm_dt(r);	\
+	free(r);		\
+	r = 0;			\
+	__i;			\
+})
+#endif
+
+#endif
diff --git a/ccan/rszshm/test/run.c b/ccan/rszshm/test/run.c
new file mode 100644
index 0000000..718af7a
--- /dev/null
+++ b/ccan/rszshm/test/run.c
@@ -0,0 +1,194 @@
+#define _GNU_SOURCE
+#include <err.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <ccan/rszshm/rszshm.h>
+#include <ccan/tap/tap.h>
+
+#include <sys/mman.h>
+#include <sys/file.h>
+
+int fail_close, fail_flock, fail_ftruncate, fail_msync, fail_munmap, fail_open;
+#define close(...)	(fail_close	? errno = 9000, -1 : close(__VA_ARGS__))
+#define flock(...)	(fail_flock	? errno = 9001, -1 : flock(__VA_ARGS__))
+#define ftruncate(...)	(fail_ftruncate	? errno = 9002, -1 : ftruncate(__VA_ARGS__))
+#define msync(...)	(fail_msync	? errno = 9003, -1 : msync(__VA_ARGS__))
+#define munmap(...)	(fail_munmap	? errno = 9004, -1 : munmap(__VA_ARGS__))
+#define open(...)	(fail_open	? errno = 9005, -1 : open(__VA_ARGS__))
+
+int fail_read, short_read;
+#define read(...)	(fail_read	? errno = 9006, -1 : short_read ? 1 : read(__VA_ARGS__))
+
+int fail_mmap_anon, fail_mmap_fixed, bad_mmap_addr;
+#define mmap(adr, len, rw, flags, fd, off) (					\
+	fail_mmap_anon  && (flags) & MAP_ANON  ? errno = 9010, MAP_FAILED :	\
+	fail_mmap_fixed && (flags) & MAP_FIXED ? errno = 9011, MAP_FAILED :	\
+	bad_mmap_addr ? NULL :							\
+	mmap(adr, len, rw, flags, fd, off)					\
+)
+#include <ccan/rszshm/rszshm.c>
+
+#define noerr(x) ({ int n = (x); if (n == -1) err(1, "%s", #x); n; })
+
+#define longstr \
+".................................................................................................................................."
+
+static jmp_buf j;
+static struct sigaction sa;
+static void segvjmp(int signum)
+{
+	longjmp(j, 1);
+}
+
+int main(void)
+{
+	plan_tests(37);
+
+	ok1(rszshm_mk(NULL, 0, NULL) == NULL && errno == EINVAL);
+
+	struct rszshm s, t;
+	ok1(rszshm_mk(&s, 0, NULL) == NULL && errno == EINVAL);
+
+	ok1(rszshm_mk(&s, 4096, longstr) == NULL && errno == EINVAL);
+
+	fail_mmap_anon = 1;
+	ok1(rszshm_mk(&s, 4096, NULL) == NULL && errno == 9010);
+	rszshm_rm(&s);
+	fail_mmap_anon = 0;
+
+	fail_open = 1;
+	ok1(rszshm_mk(&s, 4096, NULL) == NULL && errno == 9005);
+	rszshm_rm(&s);
+	fail_open = 0;
+
+	fail_ftruncate = 1;
+	ok1(rszshm_mk(&s, 4096, NULL) == NULL && errno == 9002);
+	rszshm_rm(&s);
+	fail_ftruncate = 0;
+
+	fail_mmap_fixed = 1;
+	ok1(rszshm_mk(&s, 4096, NULL) == NULL && errno == 9011);
+	rszshm_rm(&s);
+	fail_mmap_fixed = 0;
+
+	fail_msync = 1;
+	ok1(rszshm_mk(&s, 4096, NULL) == NULL && errno == 9003);
+	rszshm_rm(&s);
+	fail_msync = 0;
+
+	ok1(rszshm_mk(&s, 4096, NULL) != NULL);
+
+	struct rszshm_scan scan = RSZSHM_DFLT_SCAN;
+	scan.iter = 1;
+	ok1(rszshm_mk(&t, 4096, NULL, scan) == NULL && errno == ENOSPC);
+
+	ok1(rszshm_dt(&s) == 0);
+	ok1(rszshm_rm(&s) == 0);
+
+	long pgsz = sysconf(_SC_PAGE_SIZE);
+	scan.len = UINT64_MAX - pgsz;
+	ok1(rszshm_mk(&t, 4096, NULL, scan) == NULL && errno == ENOMEM);
+
+	ok1(rszshm_mk(&t, 4096, "foo/bar_XXXXXX/0") == NULL && errno == ENOENT);
+
+	struct rszshm *r;
+	ok1(rszshm_mkm(r, 4096, NULL) != NULL);
+
+	pid_t p, *pp;
+	noerr(p = fork());
+	char *fname = strdupa(r->fname);
+	if (p)
+		waitpid(p, NULL, 0);
+	else {
+		ok1(rszshm_free(r) == 0);
+
+		struct rszshm *q;
+		ok1(rszshm_atm(q, fname) != NULL);
+
+		*((pid_t *) q->dat) = getpid();
+
+		ok1(rszshm_up(q) == 0);
+		ok1(rszshm_grow(q) == 1);
+		ok1(rszshm_free(q) == 0);
+		exit(0);
+	}
+	pp = (pid_t *) r->dat;
+	ok1(p == *pp);
+
+	fail_mmap_fixed = 1;
+	ok1(rszshm_up(r) == -1 && errno == 9011);
+	fail_mmap_fixed = 0;
+
+	ok1(rszshm_grow(r) == 1);
+
+	ok1(rszshm_dt(r) == 0);
+
+	sa.sa_handler = segvjmp;
+	sa.sa_flags = SA_RESETHAND;
+	sigemptyset(&sa.sa_mask);
+	sigaction(SIGSEGV, &sa, NULL);
+	if (setjmp(j) == 0)
+		fail("still mapped after detach: %d", *pp);
+	else
+		pass("access after detach gives segv, OK!");
+
+	ok1(rszshm_at(r, longstr) == NULL && errno == EINVAL);
+
+	fail_open = 1;
+	ok1(rszshm_at(r, fname) == NULL && errno == 9005);
+	fail_open = 0;
+
+	fail_read = 1;
+	ok1(rszshm_at(r, fname) == NULL && errno == 9006);
+	fail_read = 0;
+
+	short_read = 1;
+	ok1(rszshm_at(r, fname) == NULL && errno == ENODATA);
+	short_read = 0;
+
+	fail_mmap_anon = 1;
+	ok1(rszshm_at(r, fname) == NULL && errno == 9010);
+	fail_mmap_anon = 0;
+
+	bad_mmap_addr = 1;
+	ok1(rszshm_at(r, fname) == NULL && errno == ENOSPC);
+	bad_mmap_addr = 0;
+
+	fail_mmap_fixed = 1;
+	ok1(rszshm_at(r, fname) == NULL && errno == 9011);
+	fail_mmap_fixed = 0;
+
+	ok1(rszshm_at(r, fname) != NULL);
+	ok1(p == *pp);
+
+	struct rszshm_hdr save = *r->hdr;
+	r->hdr->flen = r->flen;
+	r->hdr->max  = r->flen;
+	ok1(rszshm_grow(r) == -1 && errno == ENOMEM);
+	*r->hdr = save;
+
+	fail_flock = 1;
+	ok1(rszshm_grow(r) == -1 && errno == 9001);
+	fail_flock = 0;
+
+	fail_ftruncate = 1;
+	ok1(rszshm_grow(r) == -1 && errno == 9002);
+	fail_ftruncate = 0;
+
+	ok1(rszshm_grow(r) == 1);
+	ok1(rszshm_dt(r) == 0);
+	ok1(rszshm_rm(r) == 0);
+
+	r->fname[0] = '\0';
+	ok1(rszshm_rmdir(r) == -1 && errno == ENOTDIR);
+
+	ok1(rszshm_free(r) == 0);
+
+	return exit_status();
+}
-- 
2.4.3



More information about the ccan mailing list