sendmsg() で送られた fd の持つロックの所有権は保持されるのか
いくつかの *nix では sendmsg() / recvmsg() システムコールで他プロセスに fd を送信できたりします。送信元のプロセスでファイルロックを行った場合、送信先で同じ fd にファイルロックを行うとブロックするのでしょうか。出勤途中に猛烈気になったので確かめてみました。
追記: うぎゃー、unix(7) に書いてありました。
SCM_RIGHTS
他のプロセスでオープンされたファイルディスクリプタのセットを送受信する。データ部分にファイルディスクリプタの整数配列が入っている。渡されたファイルディスクリプタは、あたかも dup(2) で生成されたかのように振る舞う。
これを見落としてなければ、こんなくだらないプログラムを書かなくて済んだものを...。
ちなみに、SCM_CREDENTIALSというのもあるようです。Win32でいうとセキュリティトークンを送信できるようなものなんだろうか。
server.c:
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <netinet/in.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <stdio.h> #define _UNIX_PATH_MAX sizeof(((struct sockaddr_un *)0)->sun_path) const char *prog_name; const char *socket_path = "/tmp/aho"; int app_init(int argc, char** argv) { prog_name = argv[0] ? strrchr(argv[0], '/'): "\0(none)"; if (!prog_name) prog_name = argv[0]; else prog_name++; if (argc > 1) { size_t socket_path_len; socket_path = argv[1]; socket_path_len = strlen(socket_path); if (socket_path_len >= _UNIX_PATH_MAX) { fprintf(stderr, "%s: socket path too long (%d bytes)\n", prog_name, socket_path_len); return 1; } } return 0; } int app_run() { int s = -1, cs = -1, ffd = -1; int err = 0; s = socket(PF_UNIX, SOCK_SEQPACKET, 0); if (s < 0) { err = errno; goto out; } { struct sockaddr_un saddr = { AF_UNIX }; strncpy(saddr.sun_path, socket_path, _UNIX_PATH_MAX - 1); saddr.sun_path[_UNIX_PATH_MAX - 1] = '\0'; if (bind(s, (struct sockaddr *)&saddr, sizeof(saddr))) { err = errno; goto out; } } if (listen(s, 10)) { err = errno; goto out; } for (;; /* XXX: DON'T REPEAT AFTER ME */ (cs >= 0 ? (close(cs), cs = -1): 0), (ffd >= 0 ? (close(ffd), ffd = -1): 0)) { struct sockaddr_un csaddr; socklen_t sl = sizeof(csaddr); cs = accept(s, (struct sockaddr *)&csaddr, &sl); if (cs < 0) { err = errno; goto out; } printf("Cnnection accepted: socket = #%d.\n", cs); printf("Open file.\n"); ffd = open("/tmp/test.txt", O_RDWR); printf("Lock file (LOCK_EX).\n"); if (flock(ffd, LOCK_EX)) { fprintf(stderr, "%s: an error occurred on flock(): %s\n", prog_name, strerror(errno)); continue; } { struct msghdr msg = { 0 }; static const size_t num_fds = 1; unsigned char cmsgbuf[CMSG_SPACE(sizeof(int) * num_fds)]; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); { struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds); int *fd_list = (int *)CMSG_DATA(cmsg); fd_list[0] = ffd; /* finally fix up the controllen */ msg.msg_controllen = cmsg->cmsg_len; } if (sendmsg(cs, &msg, 0)) { fprintf(stderr, "%s: an error occurred on sendmsg(): %s\n", prog_name, strerror(errno)); continue; } printf("Message sent.\n"); printf("Try locking file again.\n"); if (flock(ffd, LOCK_EX)) { fprintf(stderr, "%s: an error occurred on flock(): %s\n", prog_name, strerror(errno)); continue; } } } out: if (s >= 0) close(s); if (err) fprintf(stderr, "%s: %s\n", prog_name, strerror(err)); return err; } int main(int argc, char** argv) { int err; err = app_init(argc, argv); if (err) return err; return app_run(); }
client.c:
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <netinet/in.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #define _UNIX_PATH_MAX sizeof(((struct sockaddr_un *)0)->sun_path) const char *prog_name; const char *socket_path = "/tmp/aho"; int app_init(int argc, char** argv) { prog_name = argv[0] ? strrchr(argv[0], '/'): "\0(none)"; if (!prog_name) prog_name = argv[0]; else prog_name++; if (argc > 1) { size_t socket_path_len; socket_path = argv[1]; socket_path_len = strlen(socket_path); if (socket_path_len >= _UNIX_PATH_MAX) { fprintf(stderr, "%s: socket path too long (%d bytes)\n", prog_name, socket_path_len); return 1; } } return 0; } int app_run() { int s = -1, ffd = -1; int err = 0; s = socket(PF_UNIX, SOCK_SEQPACKET, 0); if (s < 0) { err = errno; goto out; } { struct sockaddr_un saddr = { AF_UNIX }; strncpy(saddr.sun_path, socket_path, _UNIX_PATH_MAX - 1); saddr.sun_path[_UNIX_PATH_MAX - 1] = '\0'; if (connect(s, (struct sockaddr *)&saddr, sizeof(saddr))) { err = errno; goto out; } } { struct msghdr msg; size_t cmsgbuf_alloc = 16; void *cmsgbuf = NULL; /* Damn Posix!! msg_controllen should be set to a proper value * even if it fails. do some heuristics unwillingly */ for (;;) { size_t new_cmsgbuf_alloc = cmsgbuf_alloc << 1; void *new_cmsgbuf; if (new_cmsgbuf_alloc < cmsgbuf_alloc) { if (cmsgbuf) free(cmsgbuf); err = ENOMEM; goto out; } new_cmsgbuf = realloc(cmsgbuf, new_cmsgbuf_alloc); if (!new_cmsgbuf) { if (cmsgbuf) free(cmsgbuf); err = ENOMEM; goto out; } cmsgbuf = new_cmsgbuf; cmsgbuf_alloc = new_cmsgbuf_alloc; msg.msg_name = NULL; msg.msg_iov = NULL; msg.msg_iovlen = 0; msg.msg_control = cmsgbuf; msg.msg_controllen = cmsgbuf_alloc; if (!recvmsg(s, &msg, MSG_PEEK)) break; if (errno != EMSGSIZE) { err = errno; goto out; } } if (recvmsg(s, &msg, 0)) { err = errno; free(cmsgbuf); goto out; } { struct cmsghdr *cmsg; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { switch (cmsg->cmsg_level) { case SOL_SOCKET: if (cmsg->cmsg_type == SCM_RIGHTS) { int *fd_list = (int *)CMSG_DATA(cmsg); size_t num_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(fd_list[0]); size_t i; printf("Received fd(s): %d\n", num_fds); for (i = 0; i < num_fds; i++) { unsigned char buf[4]; ssize_t nbytesread; printf("Trying to read %zu bytes from fd #%d\n", sizeof(buf), fd_list[i]); nbytesread = read(fd_list[i], buf, sizeof(buf)); if (nbytesread < 0) { err = errno; free(cmsgbuf); goto out; } printf("%zd bytes read.\n", nbytesread); printf("Trying to flock() on fd #%d\n", fd_list[i]); if (flock(fd_list[i], LOCK_EX | LOCK_NB)) { err = errno; free(cmsgbuf); goto out; } printf("flock() succeeded.\n"); printf("Sleep 5 seconds.\n"); sleep(5); printf("Close fd #%d.\n", fd_list[i]); if (close(fd_list[i])) { fprintf(stderr, "%s: warning - close on fd #%d" " failed (%s)\n", prog_name, fd_list[i], strerror(errno)); } } } break; default: fprintf(stderr, "%s: warning - irrelevant data (level: %d)" " discarded\n", prog_name, cmsg->cmsg_level); break; } } } if (cmsgbuf) free(cmsgbuf); } out: if (s >= 0) close(s); if (err) fprintf(stderr, "%s: %s\n", prog_name, strerror(err)); return err; } int main(int argc, char** argv) { int err; err = app_init(argc, argv); if (err) return err; return app_run(); }
someone.c:
#include <unistd.h> #include <sys/types.h> #include <time.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <stdio.h> #include <stdlib.h> const char *prog_name; int app_init(int argc, char** argv) { prog_name = argv[0] ? strrchr(argv[0], '/'): "\0(none)"; if (!prog_name) prog_name = argv[0]; else prog_name++; return 0; } int app_run() { int fd = -1; int err = 0; time_t tm; printf("[%s] Open file.\n", prog_name); fd = open("/tmp/test.txt", O_RDWR); if (fd < 0) { err = errno; goto out; } printf("[%s] Try locking.\n", prog_name); tm = time(NULL); if (flock(fd, LOCK_EX)) { err = errno; goto out; } if (time(NULL) - tm >= 3) printf("[%s] I was blocked, eh?\n", prog_name); else printf("[%s] Huh?\n", prog_name); flock(fd, LOCK_UN); out: if (fd >= 0) close(fd); if (err) fprintf(stderr, "%s: %s\n", prog_name, strerror(err)); return err; } int main(int argc, char** argv) { int err; err = app_init(argc, argv); if (err) return err; return app_run(); }
実行:
% ./server & % ./client &; sleep 1; ./someone
結果:
[2] 13363 Cnnection accepted: socket = #4. Open file. Lock file (LOCK_EX). Message sent. Try locking file again. Received fd(s): 1 Trying to read 4 bytes from fd #5 4 bytes read. Trying to flock() on fd #5 flock() succeeded. Sleep 5 seconds. [someone] Open file. [someone] Try locking. Close fd #5. [someone] I was blocked, eh? [2] + done ./client
というわけで、プロセスが変わってもロックの所有権を共有するようでした。