Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 1 | /* |
| 2 | ulockmgr_server: Userspace Lock Manager Server |
| 3 | Copyright (C) 2006 Miklos Szeredi <miklos@szeredi.hu> |
| 4 | |
| 5 | This program can be distributed under the terms of the GNU GPL. |
| 6 | See the file COPYING. |
| 7 | */ |
| 8 | |
| 9 | /* #define DEBUG 1 */ |
| 10 | |
| 11 | #include <stdio.h> |
| 12 | #include <stdlib.h> |
| 13 | #include <string.h> |
| 14 | #include <unistd.h> |
| 15 | #include <fcntl.h> |
| 16 | #include <dirent.h> |
| 17 | #include <pthread.h> |
| 18 | #include <stdint.h> |
| 19 | #include <errno.h> |
| 20 | #include <assert.h> |
| 21 | #include <sys/types.h> |
| 22 | #include <sys/socket.h> |
| 23 | #include <sys/wait.h> |
| 24 | |
| 25 | struct message { |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 26 | unsigned intr : 1; |
| 27 | unsigned nofd : 1; |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 28 | pthread_t thr; |
| 29 | int cmd; |
| 30 | int fd; |
| 31 | struct flock lock; |
| 32 | int error; |
| 33 | }; |
| 34 | |
| 35 | struct fd_store { |
| 36 | struct fd_store *next; |
| 37 | int fd; |
| 38 | int origfd; |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 39 | int inuse; |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 40 | }; |
| 41 | |
| 42 | struct owner { |
| 43 | struct fd_store *fds; |
| 44 | pthread_mutex_t lock; |
| 45 | }; |
| 46 | |
| 47 | struct req_data { |
| 48 | struct owner *o; |
| 49 | int cfd; |
| 50 | struct fd_store *f; |
| 51 | struct message msg; |
| 52 | }; |
| 53 | |
| 54 | #define MAX_SEND_FDS 2 |
| 55 | |
| 56 | static int receive_message(int sock, void *buf, size_t buflen, int *fdp, |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 57 | int *numfds) |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 58 | { |
| 59 | struct msghdr msg; |
| 60 | struct iovec iov; |
| 61 | char ccmsg[CMSG_SPACE(sizeof(int)) * MAX_SEND_FDS]; |
| 62 | struct cmsghdr *cmsg; |
| 63 | int res; |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 64 | int i; |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 65 | |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 66 | assert(*numfds <= MAX_SEND_FDS); |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 67 | iov.iov_base = buf; |
| 68 | iov.iov_len = buflen; |
| 69 | |
| 70 | memset(&msg, 0, sizeof(msg)); |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 71 | memset(ccmsg, -1, sizeof(ccmsg)); |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 72 | msg.msg_iov = &iov; |
| 73 | msg.msg_iovlen = 1; |
| 74 | msg.msg_control = ccmsg; |
| 75 | msg.msg_controllen = sizeof(ccmsg); |
| 76 | |
| 77 | res = recvmsg(sock, &msg, MSG_WAITALL); |
| 78 | if (!res) |
| 79 | return 0; |
| 80 | if (res == -1) { |
| 81 | perror("ulockmgr_server: recvmsg"); |
| 82 | return -1; |
| 83 | } |
| 84 | if ((size_t) res != buflen) { |
| 85 | fprintf(stderr, "ulockmgr_server: short message received\n"); |
| 86 | return -1; |
| 87 | } |
| 88 | |
| 89 | cmsg = CMSG_FIRSTHDR(&msg); |
| 90 | if (cmsg) { |
| 91 | if (!cmsg->cmsg_type == SCM_RIGHTS) { |
| 92 | fprintf(stderr, "ulockmgr_server: unknown control message %d\n", |
| 93 | cmsg->cmsg_type); |
| 94 | return -1; |
| 95 | } |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 96 | memcpy(fdp, CMSG_DATA(cmsg), sizeof(int) * *numfds); |
| 97 | if (msg.msg_flags & MSG_CTRUNC) { |
| 98 | fprintf(stderr, "ulockmgr_server: control message truncated\n"); |
| 99 | for (i = 0; i < *numfds; i++) |
| 100 | close(fdp[i]); |
| 101 | *numfds = 0; |
| 102 | } |
| 103 | } else { |
| 104 | if (msg.msg_flags & MSG_CTRUNC) { |
| 105 | fprintf(stderr, "ulockmgr_server: control message truncated(*)\n"); |
| 106 | |
| 107 | /* There's a bug in the Linux kernel, that if not all file |
| 108 | descriptors were allocated, then the cmsg header is not |
| 109 | filled in */ |
| 110 | cmsg = (struct cmsghdr *) ccmsg; |
| 111 | memcpy(fdp, CMSG_DATA(cmsg), sizeof(int) * *numfds); |
| 112 | for (i = 0; i < *numfds; i++) |
| 113 | close(fdp[i]); |
| 114 | } |
| 115 | *numfds = 0; |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 116 | } |
| 117 | return res; |
| 118 | } |
| 119 | |
| 120 | static int closefrom(int minfd) |
| 121 | { |
| 122 | DIR *dir = opendir("/proc/self/fd"); |
| 123 | if (dir) { |
| 124 | int dfd = dirfd(dir); |
| 125 | struct dirent *ent; |
| 126 | while ((ent = readdir(dir))) { |
| 127 | char *end; |
| 128 | int fd = strtol(ent->d_name, &end, 10); |
| 129 | if (ent->d_name[0] && !end[0] && fd >= minfd && fd != dfd) |
| 130 | close(fd); |
| 131 | } |
| 132 | closedir(dir); |
| 133 | } |
| 134 | return 0; |
| 135 | } |
| 136 | |
| 137 | static void send_reply(int cfd, struct message *msg) |
| 138 | { |
| 139 | int res = send(cfd, msg, sizeof(struct message), MSG_NOSIGNAL); |
| 140 | if (res == -1) |
| 141 | perror("ulockmgr_server: sending reply"); |
| 142 | #ifdef DEBUG |
| 143 | fprintf(stderr, "ulockmgr_server: error: %i\n", msg->error); |
| 144 | #endif |
| 145 | } |
| 146 | |
| 147 | static void *process_request(void *d_) |
| 148 | { |
| 149 | struct req_data *d = d_; |
| 150 | int res; |
| 151 | |
| 152 | assert(d->msg.cmd == F_SETLKW); |
| 153 | res = fcntl(d->f->fd, F_SETLK, &d->msg.lock); |
| 154 | if (res == -1 && errno == EAGAIN) { |
| 155 | d->msg.error = EAGAIN; |
| 156 | d->msg.thr = pthread_self(); |
| 157 | send_reply(d->cfd, &d->msg); |
| 158 | res = fcntl(d->f->fd, F_SETLKW, &d->msg.lock); |
| 159 | } |
| 160 | d->msg.error = (res == -1) ? errno : 0; |
| 161 | pthread_mutex_lock(&d->o->lock); |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 162 | d->f->inuse--; |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 163 | pthread_mutex_unlock(&d->o->lock); |
| 164 | send_reply(d->cfd, &d->msg); |
| 165 | close(d->cfd); |
| 166 | free(d); |
| 167 | |
| 168 | return NULL; |
| 169 | } |
| 170 | |
| 171 | static void process_message(struct owner *o, struct message *msg, int cfd, |
| 172 | int fd) |
| 173 | { |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 174 | struct fd_store *f = NULL; |
| 175 | struct fd_store *newf = NULL; |
| 176 | struct fd_store **fp; |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 177 | struct req_data *d; |
| 178 | pthread_t tid; |
| 179 | int res; |
| 180 | |
| 181 | #ifdef DEBUG |
| 182 | fprintf(stderr, "ulockmgr_server: %i %i %i %lli %lli\n", |
| 183 | msg->cmd, msg->lock.l_type, msg->lock.l_whence, msg->lock.l_start, |
| 184 | msg->lock.l_len); |
| 185 | #endif |
| 186 | |
| 187 | if (msg->cmd == F_SETLK && msg->lock.l_type == F_UNLCK && |
| 188 | msg->lock.l_start == 0 && msg->lock.l_len == 0) { |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 189 | for (fp = &o->fds; *fp;) { |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 190 | f = *fp; |
| 191 | if (f->origfd == msg->fd && !f->inuse) { |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 192 | close(f->fd); |
| 193 | *fp = f->next; |
| 194 | free(f); |
| 195 | } else |
| 196 | fp = &f->next; |
| 197 | } |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 198 | if (!msg->nofd) |
| 199 | close(fd); |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 200 | |
| 201 | msg->error = 0; |
| 202 | send_reply(cfd, msg); |
| 203 | close(cfd); |
| 204 | return; |
| 205 | } |
| 206 | |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 207 | if (msg->nofd) { |
| 208 | for (fp = &o->fds; *fp; fp = &(*fp)->next) { |
| 209 | f = *fp; |
| 210 | if (f->origfd == msg->fd) |
| 211 | break; |
| 212 | } |
| 213 | if (!*fp) { |
| 214 | fprintf(stderr, "ulockmgr_server: fd %i not found\n", msg->fd); |
| 215 | msg->error = EIO; |
| 216 | send_reply(cfd, msg); |
| 217 | close(cfd); |
| 218 | return; |
| 219 | } |
| 220 | } else { |
| 221 | newf = f = malloc(sizeof(struct fd_store)); |
| 222 | if (!f) { |
| 223 | msg->error = ENOLCK; |
| 224 | send_reply(cfd, msg); |
| 225 | close(cfd); |
| 226 | return; |
| 227 | } |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 228 | |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 229 | f->fd = fd; |
| 230 | f->origfd = msg->fd; |
| 231 | f->inuse = 0; |
| 232 | } |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 233 | |
| 234 | if (msg->cmd == F_GETLK || msg->cmd == F_SETLK || |
| 235 | msg->lock.l_type == F_UNLCK) { |
| 236 | res = fcntl(f->fd, msg->cmd, &msg->lock); |
| 237 | msg->error = (res == -1) ? errno : 0; |
| 238 | send_reply(cfd, msg); |
| 239 | close(cfd); |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 240 | if (newf) { |
| 241 | newf->next = o->fds; |
| 242 | o->fds = newf; |
| 243 | } |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 244 | return; |
| 245 | } |
| 246 | |
| 247 | d = malloc(sizeof(struct req_data)); |
| 248 | if (!d) { |
| 249 | msg->error = ENOLCK; |
| 250 | send_reply(cfd, msg); |
| 251 | close(cfd); |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 252 | free(newf); |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 253 | return; |
| 254 | } |
| 255 | |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 256 | f->inuse++; |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 257 | d->o = o; |
| 258 | d->cfd = cfd; |
| 259 | d->f = f; |
| 260 | d->msg = *msg; |
| 261 | res = pthread_create(&tid, NULL, process_request, d); |
| 262 | if (res) { |
| 263 | msg->error = ENOLCK; |
| 264 | send_reply(cfd, msg); |
| 265 | close(cfd); |
| 266 | free(d); |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 267 | f->inuse--; |
| 268 | free(newf); |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 269 | return; |
| 270 | } |
| 271 | |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 272 | if (newf) { |
| 273 | newf->next = o->fds; |
| 274 | o->fds = newf; |
| 275 | } |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 276 | pthread_detach(tid); |
| 277 | } |
| 278 | |
| 279 | static void sigusr1_handler(int sig) |
| 280 | { |
| 281 | (void) sig; |
| 282 | /* Nothing to do */ |
| 283 | } |
| 284 | |
| 285 | static void process_owner(int cfd) |
| 286 | { |
| 287 | struct owner o; |
| 288 | struct sigaction sa; |
| 289 | |
| 290 | memset(&sa, 0, sizeof(struct sigaction)); |
| 291 | sa.sa_handler = sigusr1_handler; |
| 292 | sigemptyset(&sa.sa_mask); |
| 293 | |
| 294 | if (sigaction(SIGUSR1, &sa, NULL) == -1) { |
| 295 | perror("ulockmgr_server: cannot set sigusr1 signal handler"); |
| 296 | exit(1); |
| 297 | } |
| 298 | |
| 299 | memset(&o, 0, sizeof(struct owner)); |
| 300 | pthread_mutex_init(&o.lock, NULL); |
| 301 | while (1) { |
| 302 | struct message msg; |
| 303 | int rfds[2]; |
| 304 | int res; |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 305 | int numfds = 2; |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 306 | |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 307 | res = receive_message(cfd, &msg, sizeof(msg), rfds, &numfds); |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 308 | if (!res) |
| 309 | break; |
| 310 | if (res == -1) |
| 311 | exit(1); |
| 312 | |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 313 | if (msg.intr) { |
| 314 | if (numfds != 0) |
| 315 | fprintf(stderr, "ulockmgr_server: too many fds for intr\n"); |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 316 | pthread_kill(msg.thr, SIGUSR1); |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 317 | } else { |
| 318 | if (numfds != 2) |
| 319 | continue; |
| 320 | |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 321 | pthread_mutex_lock(&o.lock); |
| 322 | process_message(&o, &msg, rfds[0], rfds[1]); |
| 323 | pthread_mutex_unlock(&o.lock); |
| 324 | } |
| 325 | } |
| 326 | if (o.fds) |
| 327 | fprintf(stderr, "ulockmgr_server: open file descriptors on exit\n"); |
| 328 | } |
| 329 | |
| 330 | int main(int argc, char *argv[]) |
| 331 | { |
| 332 | int nullfd; |
| 333 | char *end; |
| 334 | int cfd; |
| 335 | sigset_t empty; |
| 336 | |
| 337 | if (argc != 2 || !argv[1][0]) |
| 338 | goto out_inval; |
| 339 | |
| 340 | cfd = strtol(argv[1], &end, 10); |
| 341 | if (*end) |
| 342 | goto out_inval; |
| 343 | |
| 344 | if (daemon(0, 1) == -1) { |
| 345 | perror("ulockmgr_server: daemon"); |
| 346 | exit(1); |
| 347 | } |
| 348 | |
| 349 | sigemptyset(&empty); |
| 350 | sigprocmask(SIG_SETMASK, &empty, NULL); |
| 351 | |
| 352 | if (dup2(cfd, 4) == -1) { |
| 353 | perror("ulockmgr_server: dup2"); |
| 354 | exit(1); |
| 355 | } |
| 356 | cfd = 4; |
| 357 | nullfd = open("/dev/null", O_RDWR); |
| 358 | dup2(nullfd, 0); |
| 359 | dup2(nullfd, 1); |
| 360 | close(3); |
| 361 | closefrom(5); |
| 362 | while (1) { |
| 363 | char c; |
| 364 | int sock; |
| 365 | int pid; |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 366 | int numfds = 1; |
| 367 | int res = receive_message(cfd, &c, sizeof(c), &sock, &numfds); |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 368 | if (!res) |
| 369 | break; |
| 370 | if (res == -1) |
| 371 | exit(1); |
Miklos Szeredi | 7e7fa1f | 2006-10-08 15:41:20 +0000 | [diff] [blame^] | 372 | assert(numfds == 1); |
Miklos Szeredi | 0c59ebf | 2006-09-10 20:53:36 +0000 | [diff] [blame] | 373 | |
| 374 | pid = fork(); |
| 375 | if (pid == -1) { |
| 376 | perror("ulockmgr_server: fork"); |
| 377 | close(sock); |
| 378 | continue; |
| 379 | } |
| 380 | if (pid == 0) { |
| 381 | close(cfd); |
| 382 | pid = fork(); |
| 383 | if (pid == -1) { |
| 384 | perror("ulockmgr_server: fork"); |
| 385 | _exit(1); |
| 386 | } |
| 387 | if (pid == 0) |
| 388 | process_owner(sock); |
| 389 | _exit(0); |
| 390 | } |
| 391 | waitpid(pid, NULL, 0); |
| 392 | close(sock); |
| 393 | } |
| 394 | return 0; |
| 395 | |
| 396 | out_inval: |
| 397 | fprintf(stderr, "%s should be started by libulockmgr\n", argv[0]); |
| 398 | return 1; |
| 399 | } |