/* ulockmgr_server: Userspace Lock Manager Server Copyright (C) 2006 Miklos Szeredi This program can be distributed under the terms of the GNU GPL. See the file COPYING. */ /* #define DEBUG 1 */ #include #include #include #include #include #include #include #include #include #include #include #include #include struct message { unsigned intr : 1; unsigned nofd : 1; pthread_t thr; int cmd; int fd; struct flock lock; int error; }; struct fd_store { struct fd_store *next; int fd; int origfd; int inuse; }; struct owner { struct fd_store *fds; pthread_mutex_t lock; }; struct req_data { struct owner *o; int cfd; struct fd_store *f; struct message msg; }; #define MAX_SEND_FDS 2 static int receive_message(int sock, void *buf, size_t buflen, int *fdp, int *numfds) { struct msghdr msg; struct iovec iov; size_t ccmsg[CMSG_SPACE(sizeof(int) * MAX_SEND_FDS) / sizeof(size_t)]; struct cmsghdr *cmsg; int res; int i; assert(*numfds <= MAX_SEND_FDS); iov.iov_base = buf; iov.iov_len = buflen; memset(&msg, 0, sizeof(msg)); memset(ccmsg, -1, sizeof(ccmsg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = ccmsg; msg.msg_controllen = sizeof(ccmsg); res = recvmsg(sock, &msg, MSG_WAITALL); if (!res) { /* retry on zero return, see do_recv() in ulockmgr.c */ res = recvmsg(sock, &msg, MSG_WAITALL); if (!res) return 0; } if (res == -1) { perror("ulockmgr_server: recvmsg"); return -1; } if ((size_t) res != buflen) { fprintf(stderr, "ulockmgr_server: short message received\n"); return -1; } cmsg = CMSG_FIRSTHDR(&msg); if (cmsg) { if (cmsg->cmsg_type != SCM_RIGHTS) { fprintf(stderr, "ulockmgr_server: unknown control message %d\n", cmsg->cmsg_type); return -1; } memcpy(fdp, CMSG_DATA(cmsg), sizeof(int) * *numfds); if (msg.msg_flags & MSG_CTRUNC) { fprintf(stderr, "ulockmgr_server: control message truncated\n"); for (i = 0; i < *numfds; i++) close(fdp[i]); *numfds = 0; } } else { if (msg.msg_flags & MSG_CTRUNC) { fprintf(stderr, "ulockmgr_server: control message truncated(*)\n"); /* There's a bug in the Linux kernel, that if not all file descriptors were allocated, then the cmsg header is not filled in */ cmsg = (struct cmsghdr *) ccmsg; memcpy(fdp, CMSG_DATA(cmsg), sizeof(int) * *numfds); for (i = 0; i < *numfds; i++) close(fdp[i]); } *numfds = 0; } return res; } static int closefrom(int minfd) { DIR *dir = opendir("/proc/self/fd"); if (dir) { int dfd = dirfd(dir); struct dirent *ent; while ((ent = readdir(dir))) { char *end; int fd = strtol(ent->d_name, &end, 10); if (ent->d_name[0] && !end[0] && fd >= minfd && fd != dfd) close(fd); } closedir(dir); } return 0; } static void send_reply(int cfd, struct message *msg) { int res = send(cfd, msg, sizeof(struct message), MSG_NOSIGNAL); if (res == -1) perror("ulockmgr_server: sending reply"); #ifdef DEBUG fprintf(stderr, "ulockmgr_server: error: %i\n", msg->error); #endif } static void *process_request(void *d_) { struct req_data *d = d_; int res; assert(d->msg.cmd == F_SETLKW); res = fcntl(d->f->fd, F_SETLK, &d->msg.lock); if (res == -1 && errno == EAGAIN) { d->msg.error = EAGAIN; d->msg.thr = pthread_self(); send_reply(d->cfd, &d->msg); res = fcntl(d->f->fd, F_SETLKW, &d->msg.lock); } d->msg.error = (res == -1) ? errno : 0; pthread_mutex_lock(&d->o->lock); d->f->inuse--; pthread_mutex_unlock(&d->o->lock); send_reply(d->cfd, &d->msg); close(d->cfd); free(d); return NULL; } static void process_message(struct owner *o, struct message *msg, int cfd, int fd) { struct fd_store *f = NULL; struct fd_store *newf = NULL; struct fd_store **fp; struct req_data *d; pthread_t tid; int res; #ifdef DEBUG fprintf(stderr, "ulockmgr_server: %i %i %i %lli %lli\n", msg->cmd, msg->lock.l_type, msg->lock.l_whence, msg->lock.l_start, msg->lock.l_len); #endif if (msg->cmd == F_SETLK && msg->lock.l_type == F_UNLCK && msg->lock.l_start == 0 && msg->lock.l_len == 0) { for (fp = &o->fds; *fp;) { f = *fp; if (f->origfd == msg->fd && !f->inuse) { close(f->fd); *fp = f->next; free(f); } else fp = &f->next; } if (!msg->nofd) close(fd); msg->error = 0; send_reply(cfd, msg); close(cfd); return; } if (msg->nofd) { for (fp = &o->fds; *fp; fp = &(*fp)->next) { f = *fp; if (f->origfd == msg->fd) break; } if (!*fp) { fprintf(stderr, "ulockmgr_server: fd %i not found\n", msg->fd); msg->error = EIO; send_reply(cfd, msg); close(cfd); return; } } else { newf = f = malloc(sizeof(struct fd_store)); if (!f) { msg->error = ENOLCK; send_reply(cfd, msg); close(cfd); return; } f->fd = fd; f->origfd = msg->fd; f->inuse = 0; } if (msg->cmd == F_GETLK || msg->cmd == F_SETLK || msg->lock.l_type == F_UNLCK) { res = fcntl(f->fd, msg->cmd, &msg->lock); msg->error = (res == -1) ? errno : 0; send_reply(cfd, msg); close(cfd); if (newf) { newf->next = o->fds; o->fds = newf; } return; } d = malloc(sizeof(struct req_data)); if (!d) { msg->error = ENOLCK; send_reply(cfd, msg); close(cfd); free(newf); return; } f->inuse++; d->o = o; d->cfd = cfd; d->f = f; d->msg = *msg; res = pthread_create(&tid, NULL, process_request, d); if (res) { msg->error = ENOLCK; send_reply(cfd, msg); close(cfd); free(d); f->inuse--; free(newf); return; } if (newf) { newf->next = o->fds; o->fds = newf; } pthread_detach(tid); } static void sigusr1_handler(int sig) { (void) sig; /* Nothing to do */ } static void process_owner(int cfd) { struct owner o; struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sigusr1_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGUSR1, &sa, NULL) == -1) { perror("ulockmgr_server: cannot set sigusr1 signal handler"); exit(1); } memset(&o, 0, sizeof(struct owner)); pthread_mutex_init(&o.lock, NULL); while (1) { struct message msg; int rfds[2]; int res; int numfds = 2; res = receive_message(cfd, &msg, sizeof(msg), rfds, &numfds); if (!res) break; if (res == -1) exit(1); if (msg.intr) { if (numfds != 0) fprintf(stderr, "ulockmgr_server: too many fds for intr\n"); pthread_kill(msg.thr, SIGUSR1); } else { if (numfds != 2) continue; pthread_mutex_lock(&o.lock); process_message(&o, &msg, rfds[0], rfds[1]); pthread_mutex_unlock(&o.lock); } } if (o.fds) fprintf(stderr, "ulockmgr_server: open file descriptors on exit\n"); } int main(int argc, char *argv[]) { int nullfd; char *end; int cfd; sigset_t empty; if (argc != 2 || !argv[1][0]) goto out_inval; cfd = strtol(argv[1], &end, 10); if (*end) goto out_inval; /* demonize current process */ switch(fork()) { case -1: perror("ulockmgr_server: fork"); exit(1); case 0: break; default: _exit(0); } if (setsid() == -1) { perror("ulockmgr_server: setsid"); exit(1); } (void) chdir("/"); sigemptyset(&empty); sigprocmask(SIG_SETMASK, &empty, NULL); if (dup2(cfd, 4) == -1) { perror("ulockmgr_server: dup2"); exit(1); } cfd = 4; nullfd = open("/dev/null", O_RDWR); if (nullfd >= 0) { dup2(nullfd, 0); dup2(nullfd, 1); } close(3); closefrom(5); while (1) { char c; int sock; int pid; int numfds = 1; int res = receive_message(cfd, &c, sizeof(c), &sock, &numfds); if (!res) break; if (res == -1) exit(1); assert(numfds == 1); pid = fork(); if (pid == -1) { perror("ulockmgr_server: fork"); close(sock); continue; } if (pid == 0) { close(cfd); pid = fork(); if (pid == -1) { perror("ulockmgr_server: fork"); _exit(1); } if (pid == 0) process_owner(sock); _exit(0); } waitpid(pid, NULL, 0); close(sock); } return 0; out_inval: fprintf(stderr, "%s should be started by libulockmgr\n", argv[0]); return 1; }