From: Dan Good Date: Tue, 19 Jan 2016 06:04:02 +0000 (+0000) Subject: rszshm: New module X-Git-Url: http://git.ozlabs.org/?p=ccan;a=commitdiff_plain;h=ac29ec5d15e8b108cb75cfb4fe13d79739e9c5cf;hp=64e9e7145aac9502655c5799ab711b9766c1da57 rszshm: New module rszshm - resizable pointer-safe shared memory Signed-off-by: Dan Good Signed-off-by: Rusty Russell --- diff --git a/Makefile-ccan b/Makefile-ccan index 8d99bbf4..9469334e 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 00000000..4f8ee740 --- /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 00000000..2134e671 --- /dev/null +++ b/ccan/rszshm/_info @@ -0,0 +1,96 @@ +#include "config.h" +#include +#include + +/** + * 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 + * #include + * #include + * #include + * #include + * #include + * + * #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 + * + * 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 00000000..32db4556 --- /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 +#include +#include +#include +#include +#include +#include +#include + +#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 00000000..c29e4a5b --- /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 +#include +#include + +/** + * 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 00000000..718af7a5 --- /dev/null +++ b/ccan/rszshm/test/run.c @@ -0,0 +1,194 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +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 + +#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(); +}