#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <signal.h>
int cmp_and_exg(volatile int *lockp, int old, int new);
struct umtx {
volatile int lock;
};
#define MTX_LOCKED 0x80000000
static int userland_get_mutex(struct umtx *mtx, int timo);
static int userland_get_mutex_contested(struct umtx *mtx, int timo);
static void userland_rel_mutex(struct umtx *mtx);
static void userland_rel_mutex_contested(struct umtx *mtx);
static void docleanup(int signo);
static struct umtx *cleanup_mtx_contested;
static struct umtx *cleanup_mtx_held;
int verbose_opt;
int
main(int ac, char **av)
{
char *path;
char *str;
off_t off = 0;
pid_t pid;
int ch;
int fd;
int pgsize;
int pgmask;
int timo = 0;
struct stat st;
struct umtx *mtx;
signal(SIGINT, docleanup);
while ((ch = getopt(ac, av, "t:v")) != -1) {
switch(ch) {
case 't':
timo = strtol(optarg, NULL, 0);
break;
case 'v':
verbose_opt = 1;
break;
default:
fprintf(stderr, "unknown option: -%c\n", optopt);
exit(1);
}
}
ac -= optind;
av += optind;
if (ac < 2) {
fprintf(stderr, "umtx file[:offset] command\n");
exit(1);
}
path = av[0];
if ((str = strchr(path, ':')) != NULL) {
*str++ = 0;
off = strtoull(str, NULL, 0);
}
if ((fd = open(path, O_RDWR|O_CREAT, 0666)) < 0) {
perror("open");
exit(1);
}
if (fstat(fd, &st) < 0) {
perror("fstat");
exit(1);
}
if (off + 4 > st.st_size) {
int v = 0;
lseek(fd, off, 0);
write(fd, &v, sizeof(v));
}
pgsize = getpagesize();
pgmask = pgsize - 1;
str = mmap(NULL, pgsize, PROT_READ|PROT_WRITE, MAP_SHARED,
fd, off & ~(off_t)pgmask);
mtx = (struct umtx *)(str + ((int)off & pgmask));
if (userland_get_mutex(mtx, timo) < 0) {
fprintf(stderr, "Mutex at %s:%ld timed out\n", path, off);
exit(1);
}
if (verbose_opt)
fprintf(stderr, "Obtained mutex at %s:%ld\n", path, off);
if ((pid = fork()) == 0) {
execvp(av[1], av + 1);
_exit(0);
} else if (pid > 0) {
while (waitpid(pid, NULL, 0) != pid)
;
} else {
fprintf(stderr, "Unable to exec %s\n", av[1]);
}
userland_rel_mutex(mtx);
close(fd);
return(0);
}
static int
userland_get_mutex(struct umtx *mtx, int timo)
{
int v;
for (;;) {
v = mtx->lock;
if ((v & MTX_LOCKED) == 0) {
if (cmp_and_exg(&mtx->lock, v, v | MTX_LOCKED) == 0) {
cleanup_mtx_held = mtx;
return(0);
}
} else {
if (cmp_and_exg(&mtx->lock, v, v + 1) == 0) {
cleanup_mtx_contested = mtx;
return(userland_get_mutex_contested(mtx, timo));
}
}
}
}
static int
userland_get_mutex_contested(struct umtx *mtx, int timo)
{
int v;
for (;;) {
v = mtx->lock;
assert(v & ~MTX_LOCKED);
if ((v & MTX_LOCKED) == 0) {
if (cmp_and_exg(&mtx->lock, v, (v - 1) | MTX_LOCKED) == 0) {
cleanup_mtx_contested = NULL;
cleanup_mtx_held = mtx;
return(0);
}
} else {
if (verbose_opt)
fprintf(stderr, "waiting on mutex timeout=%d\n", timo);
if (timo == 0) {
umtx_sleep(&mtx->lock, v, 0);
} else {
if (umtx_sleep(&mtx->lock, v, 1000000) < 0) {
if (errno == EAGAIN && --timo == 0) {
cleanup_mtx_contested = NULL;
userland_rel_mutex_contested(mtx);
return(-1);
}
}
}
}
}
}
static void
userland_rel_mutex(struct umtx *mtx)
{
int v;
for (;;) {
v = mtx->lock;
assert(v & MTX_LOCKED);
if (v == MTX_LOCKED) {
if (cmp_and_exg(&mtx->lock, MTX_LOCKED, 0) == 0) {
if (verbose_opt)
fprintf(stderr, "releasing uncontested mutex\n");
return;
}
} else {
if (cmp_and_exg(&mtx->lock, v, v & ~MTX_LOCKED) == 0) {
umtx_wakeup(&mtx->lock, 1);
if (verbose_opt)
fprintf(stderr, "releasing contested mutex\n");
return;
}
}
}
}
static void
userland_rel_mutex_contested(struct umtx *mtx)
{
int v;
for (;;) {
if (cmp_and_exg(&mtx->lock, v, v - 1) == 0)
return;
v = mtx->lock;
assert(v & ~MTX_LOCKED);
}
}
static void
docleanup(int signo)
{
printf("cleanup\n");
if (cleanup_mtx_contested)
userland_rel_mutex_contested(cleanup_mtx_contested);
if (cleanup_mtx_held)
userland_rel_mutex(cleanup_mtx_held);
exit(1);
}
__asm(
" .text\n"
"cmp_and_exg:\n"
" movl 4(%esp),%ebx\n"
" movl 8(%esp),%eax\n"
" movl 12(%esp),%edx\n"
" lock cmpxchgl %edx,(%ebx)\n"
" jz 1f\n"
" movl $-1,%eax\n"
" ret\n"
"1:\n"
" subl %eax,%eax\n"
" ret\n"
);