// socktest -- prove that Unix domain sockets carry data between processes. // // Every part of a socket is invisible from a return value. bind() returning 0 // proves nothing; a socket layer that accepts every call and drops every byte // would pass a test built on return codes alone. So each check here has one // process write and a *different* process read, and compares the bytes. // // socktest run every check // // Prints one line per check and a final verdict a script can grep for. #include "core/types.h" #include "uapi/user.h" #include "net/socket.h" #include "uapi/stat.h" #include "uapi/fcntl.h" static int failures; static void puts_(const char *s) { int n = 0; while (s[n]) n++; write(1, (void*)s, n); } static void putn_(int v) { char buf[12]; int i = 0, neg = 0; if (v < 0) { neg = 1; v = -v; } if (v == 0) buf[i++] = '0'; while (v) { buf[i++] = '0' + (v % 10); v /= 10; } if (neg) buf[i++] = '-'; while (i--) write(1, &buf[i], 1); } static void check(int ok, const char *what) { puts_(ok ? " [ok] " : " FAIL: "); puts_(what); puts_("\n"); if (!ok) failures++; } static int streq(const char *a, const char *b) { while (*a && *a == *b) { a++; b++; } return *a == *b; } // Fill a sockaddr_un and return the length to pass to bind/connect: the // family plus the path plus its NUL, which is what musl computes and what the // kernel side is written to expect. // Every bind() here is preceded by an unlink(), which is not tidiness: a bound // socket is a *file* now, and it outlives the process that bound it. Rebinding // a path that already has a node is EADDRINUSE, exactly as on Linux, so every // real server removes the stale one first. Before this change the name lived // only in the kernel's socket table and vanished when the process did, so a // test could bind the same path on every run and never notice. static int mkaddr(struct sockaddr_un *sa, const char *path) { int i = 0; for (i = 0; i < UNIX_PATH_MAX - 1 && path[i]; i++) sa->sun_path[i] = path[i]; sa->sun_path[i] = '\0'; sa->sun_family = AF_UNIX; return (int)sizeof(sa->sun_family) + i + 1; } // --------------------------------------------------------------------------- // 1. A round trip over a named socket, between two processes. // --------------------------------------------------------------------------- static void test_roundtrip(void) { struct sockaddr_un sa; int len; unlink("/tmp/socktest"); // a stale node from an earlier run len = mkaddr(&sa, "/tmp/socktest"); int srv, pid, cli, n; char buf[64]; puts_("named socket, two processes:\n"); srv = socket(AF_UNIX, SOCK_STREAM, 0); check(srv >= 0, "socket() returns a descriptor"); if (srv < 0) return; check(bind(srv, (struct sockaddr *)&sa, len) == 0, "bind() to /tmp/socktest"); check(listen(srv, 4) == 0, "listen()"); // A second bind to the same name must be refused, or two servers can // silently steal each other's clients. { int dup_ = socket(AF_UNIX, SOCK_STREAM, 0); check(bind(dup_, (struct sockaddr *)&sa, len) < 0, "a second bind to the same name is refused"); close(dup_); } pid = fork(); if (pid == 0) { // Child: the client. Connects, sends a question, reads the answer. int c = socket(AF_UNIX, SOCK_STREAM, 0); char reply[64]; int r; close(srv); if (connect(c, (struct sockaddr *)&sa, len) < 0) { puts_(" FAIL: child connect()\n"); exit(); } write(c, "ping", 4); r = read(c, reply, sizeof(reply) - 1); if (r < 0) r = 0; reply[r] = '\0'; if (!streq(reply, "pong")) puts_(" FAIL: child did not receive pong\n"); close(c); exit(); } cli = accept(srv, 0, 0); check(cli >= 0, "accept() returns a connection"); if (cli < 0) { close(srv); wait(); return; } n = read(cli, buf, sizeof(buf) - 1); if (n < 0) n = 0; buf[n] = '\0'; check(streq(buf, "ping"), "server read the client's bytes"); check(write(cli, "pong", 4) == 4, "server replied on the same socket"); close(cli); close(srv); wait(); } // --------------------------------------------------------------------------- // 2. Errors that must actually be errors. // --------------------------------------------------------------------------- static void test_errors(void) { struct sockaddr_un sa; int len; unlink("/tmp/nobody-listening"); // a stale node from an earlier run len = mkaddr(&sa, "/tmp/nobody-listening"); int s; puts_("error paths:\n"); s = socket(AF_UNIX, SOCK_STREAM, 0); check(connect(s, (struct sockaddr *)&sa, len) < 0, "connect() to a name nobody bound fails"); close(s); // This used to assert that AF_INET was *refused*, which was true when this // file was written and stopped being true the day AF_INET landed. It kept // passing anyway, and the reason is worth keeping: socktest runs seconds // after boot, DHCP had not bound yet, and inet_new() refused every socket // until it did -- reported as ENFILE. So the check saw "< 0" and called it // a pass for a reason that had nothing to do with what it was testing. // // Two lessons, both already written down elsewhere in this tree and both // learned again here: a test that asserts a *failure* has to say which // failure, and a green suite is not evidence until something has checked // that it can go red. // // What is left are the refusals that are unconditionally true, whatever the // link is doing. check(socket(99, SOCK_STREAM, 0) < 0, "an unknown address family is refused"); check(socket(AF_INET, SOCK_STREAM, 99) < 0, "AF_INET with a nonsense protocol is refused"); check(socket(AF_UNIX, SOCK_DGRAM, 0) < 0, "SOCK_DGRAM is refused, not faked"); // read/write on a plain file descriptor must not be mistaken for a socket, // and socket calls on one must be refused. check(listen(1, 4) < 0, "listen() on a non-socket fd fails"); } // --------------------------------------------------------------------------- // 3. End-of-file. A reader whose peer closes must be told, not left hanging. // --------------------------------------------------------------------------- static void test_eof(void) { struct sockaddr_un sa; int len; unlink("/tmp/socktest-eof"); // a stale node from an earlier run len = mkaddr(&sa, "/tmp/socktest-eof"); int srv, cli, pid, n; char buf[16]; puts_("end of file:\n"); srv = socket(AF_UNIX, SOCK_STREAM, 0); bind(srv, (struct sockaddr *)&sa, len); listen(srv, 4); pid = fork(); if (pid == 0) { int c = socket(AF_UNIX, SOCK_STREAM, 0); close(srv); connect(c, (struct sockaddr *)&sa, len); write(c, "x", 1); close(c); // the peer goes away with the byte still unread exit(); } cli = accept(srv, 0, 0); n = read(cli, buf, sizeof(buf)); check(n == 1, "the byte sent before close() is still delivered"); n = read(cli, buf, sizeof(buf)); check(n == 0, "the next read returns 0 rather than blocking"); close(cli); close(srv); wait(); } // --------------------------------------------------------------------------- // 4. socketpair: bidirectional, no name, no listener. // --------------------------------------------------------------------------- static void test_socketpair(void) { int sv[2], n; char buf[16]; puts_("socketpair:\n"); if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { check(0, "socketpair() succeeds"); return; } check(1, "socketpair() succeeds"); write(sv[0], "a", 1); n = read(sv[1], buf, sizeof(buf)); check(n == 1 && buf[0] == 'a', "0 -> 1 carries a byte"); write(sv[1], "b", 1); n = read(sv[0], buf, sizeof(buf)); check(n == 1 && buf[0] == 'b', "1 -> 0 carries a byte, on the same pair"); close(sv[0]); close(sv[1]); } // --------------------------------------------------------------------------- // 5. A message longer than the socket buffer, so the writer has to block and // the reader has to drain in more than one go. // --------------------------------------------------------------------------- #define BIG 12000 static void test_big(void) { int sv[2], pid, got = 0, ok = 1, i; static char out[BIG], in[BIG]; puts_("a transfer larger than the buffer:\n"); for (i = 0; i < BIG; i++) out[i] = (char)('A' + (i % 26)); if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { check(0, "socketpair() for the big transfer"); return; } pid = fork(); if (pid == 0) { int sent = 0, r; close(sv[1]); while (sent < BIG) { r = write(sv[0], out + sent, BIG - sent); if (r <= 0) break; sent += r; } close(sv[0]); exit(); } close(sv[0]); while (got < BIG) { int r = read(sv[1], in + got, BIG - got); if (r <= 0) break; got += r; } close(sv[1]); wait(); check(got == BIG, "every byte arrived"); if (got != BIG) { puts_(" got "); putn_(got); puts_(" of "); putn_(BIG); puts_("\n"); } for (i = 0; i < got; i++) if (in[i] != out[i]) { ok = 0; break; } check(ok, "and in the right order"); } // --------------------------------------------------------------------------- // Socket options. // // Both calls used to succeed unconditionally: setsockopt was `return 0;` and // getsockopt wrote a zero for whatever was asked. So the only check that proves // anything is a *round trip* -- set a value, read it back, and see the value // rather than a zero -- plus one option that must be refused. A stub passes // "does setsockopt return 0"; it cannot pass both of these at once. // --------------------------------------------------------------------------- #define T_SOL_SOCKET 1 #define T_SO_REUSEADDR 2 #define T_SO_TYPE 3 #define T_SO_RCVBUF 8 #define T_SO_NOSUCH 0x7ffe // not an option Linux defines either static void test_sockopt(void) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); int val, len; puts_("-- socket options\n"); if (fd < 0) { check(0, "socket()"); return; } // SO_TYPE must report what this socket actually is, not zero. val = -1; len = sizeof(val); check(getsockopt(fd, T_SOL_SOCKET, T_SO_TYPE, &val, &len) == 0 && val == SOCK_STREAM, "getsockopt SO_TYPE says SOCK_STREAM"); // The round trip. A stub that ignores the set and zeroes the get fails here. val = 1; check(setsockopt(fd, T_SOL_SOCKET, T_SO_REUSEADDR, &val, sizeof(val)) == 0, "setsockopt SO_REUSEADDR accepted"); val = -1; len = sizeof(val); check(getsockopt(fd, T_SOL_SOCKET, T_SO_REUSEADDR, &val, &len) == 0 && val == 1, "getsockopt SO_REUSEADDR reads back 1"); // And back off again, so the answer is not just "always non-zero". val = 0; setsockopt(fd, T_SOL_SOCKET, T_SO_REUSEADDR, &val, sizeof(val)); val = -1; len = sizeof(val); check(getsockopt(fd, T_SOL_SOCKET, T_SO_REUSEADDR, &val, &len) == 0 && val == 0, "and reads back 0 once cleared"); // A real buffer size, not zero. val = -1; len = sizeof(val); check(getsockopt(fd, T_SOL_SOCKET, T_SO_RCVBUF, &val, &len) == 0 && val > 0, "getsockopt SO_RCVBUF is a real size"); // The refusal. This is the half that a permissive stub cannot fake. val = 1; check(setsockopt(fd, T_SOL_SOCKET, T_SO_NOSUCH, &val, sizeof(val)) < 0, "setsockopt of an unknown option is refused"); len = sizeof(val); check(getsockopt(fd, T_SOL_SOCKET, T_SO_NOSUCH, &val, &len) < 0, "getsockopt of an unknown option is refused"); close(fd); } // --------------------------------------------------------------------------- // SOCK_NONBLOCK, end to end. // // Checking that socket() accepts the flag proves nothing -- it was accepted // before, and discarded. What proves it is a read that *returns* on a socket // with nothing in it, where a blocking socket would sleep forever. // --------------------------------------------------------------------------- #define T_SOCK_NONBLOCK 0x800 #define T_F_GETFL 3 #define T_O_NONBLOCK 04000 static void test_nonblock(void) { int sv[2]; char c; puts_("-- SOCK_NONBLOCK\n"); if (socketpair(AF_UNIX, SOCK_STREAM | T_SOCK_NONBLOCK, 0, sv) < 0) { check(0, "socketpair(SOCK_NONBLOCK)"); return; } check((fcntl(sv[0], T_F_GETFL, 0) & T_O_NONBLOCK) != 0, "fcntl(F_GETFL) reports O_NONBLOCK"); // Nothing has been written, so a blocking read would never return. If this // test hangs, the flag was dropped -- which is exactly what it used to be. check(read(sv[0], &c, 1) < 0, "read on an empty non-blocking socket returns"); // And it still carries data, so the flag did not break the socket. write(sv[1], "x", 1); c = 0; check(read(sv[0], &c, 1) == 1 && c == 'x', "and still delivers a byte"); close(sv[0]); close(sv[1]); } // The user library has no string.h; these three are all this file needs. static void memset_(void *p, int c, int n) { char *d = p; while (n-- > 0) *d++ = (char)c; } static void memmove_(void *dst, const void *src, int n) { char *d = dst; const char *s = src; while (n-- > 0) *d++ = *s++; } static int strneq_(const char *a, const char *b, int n) { while (n-- > 0 && *a == *b && *a) { a++; b++; } return n < 0 || *a == *b; } // --------------------------------------------------------------------------- // 8. SCM_RIGHTS: a descriptor crossing between processes. // // docs/architecture/kernel.md item 1, docs/architecture/devices.md mechanism 1. The thing // being proved is not that recvmsg returns a number -- it is that the number // names the *same open file* the other process had, which is why every check // below moves data through the descriptor rather than inspecting it. // // The descriptor passed is always a pipe end, for one reason: a pipe announces // its own lifetime. A read of a pipe whose every write end is closed returns 0, // and that turns "was the kernel's reference released" -- which userspace // cannot otherwise see -- into a value a test can compare. // --------------------------------------------------------------------------- #define T_F_SETFL 4 // Send `fd` with one byte of data on it. static int sendfd(int sock, int fd, char tag) { struct msghdr m; struct iovec iv; char cbuf[CMSG_SPACE(sizeof(int))]; struct cmsghdr *c; memset_(&m, 0, sizeof(m)); memset_(cbuf, 0, sizeof(cbuf)); iv.iov_base = &tag; iv.iov_len = 1; m.msg_iov = &iv; m.msg_iovlen = 1; m.msg_control = cbuf; m.msg_controllen = sizeof(cbuf); c = (struct cmsghdr *)cbuf; c->cmsg_len = CMSG_LEN(sizeof(int)); c->cmsg_level = SOL_SOCKET; c->cmsg_type = SCM_RIGHTS; memmove_(CMSG_DATA(c), &fd, sizeof(int)); return sendmsg(sock, &m, 0); } // Receive into `fds`, up to `want` of them. Returns the byte count; *ngot is // how many descriptors arrived and *flags the kernel's msg_flags. static int recvfd(int sock, char *tag, int *fds, int want, int *ngot, int *flags) { struct msghdr m; struct iovec iv; char cbuf[CMSG_SPACE(SCM_MAXFD * sizeof(int))]; struct cmsghdr *c; int n; memset_(&m, 0, sizeof(m)); memset_(cbuf, 0, sizeof(cbuf)); iv.iov_base = tag; iv.iov_len = 1; m.msg_iov = &iv; m.msg_iovlen = 1; m.msg_control = cbuf; // CMSG_LEN, not CMSG_SPACE: the trailing padding only matters if another // header follows, and sizing by it would leave room for a descriptor the // caller did not ask for -- which is exactly what makes a truncation test // pass by accident. m.msg_controllen = CMSG_LEN(want * sizeof(int)); *ngot = 0; n = recvmsg(sock, &m, 0); if (flags) *flags = m.msg_flags; if (n < 0) return n; c = CMSG_FIRSTHDR(&m); if (c && c->cmsg_level == SOL_SOCKET && c->cmsg_type == SCM_RIGHTS) { int k = (int)((c->cmsg_len - CMSG_LEN(0)) / sizeof(int)); for (*ngot = 0; *ngot < k && *ngot < want; (*ngot)++) memmove_(&fds[*ngot], CMSG_DATA(c) + *ngot * sizeof(int), sizeof(int)); } return n; } // Is every write end of this pipe gone? Non-blocking, so a descriptor that // leaked reports itself instead of hanging the test. static int pipe_at_eof(int rfd) { char c; fcntl(rfd, T_F_SETFL, T_O_NONBLOCK); return read(rfd, &c, 1) == 0; } static void test_fdpass(void) { int sv[2], p[2], pid, got = 0, fds[SCM_MAXFD], n; char tag = 0, buf[32]; puts_("SCM_RIGHTS:\n"); if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0 || pipe(p) < 0) { check(0, "socketpair() and pipe() for the descriptor tests"); return; } pid = fork(); if (pid == 0) { close(sv[0]); write(p[1], "through a passed descriptor", 27); sendfd(sv[1], p[0], 'F'); // Every process-held reference is gone from here on. If the kernel is // not holding one of its own, the parent receives a dead descriptor. close(p[0]); close(p[1]); close(sv[1]); exit(); } close(sv[1]); close(p[0]); close(p[1]); wait(); n = recvfd(sv[0], &tag, fds, SCM_MAXFD, &got, 0); check(n == 1 && tag == 'F', "recvmsg returns the byte the descriptor rode on"); check(got == 1, "and one descriptor with it"); if (got == 1) { n = read(fds[0], buf, sizeof(buf)); check(n == 27 && strneq_(buf, "through a passed descriptor", 27), "the descriptor is the sender's pipe, and still has its data"); close(fds[0]); } close(sv[0]); } // Two messages, each carrying a descriptor, read with a buffer big enough for // both. A read that ran past the second message's first byte would hand back // one message's descriptors with two messages' bytes, and nothing in the // result would say so -- which is why the kernel stops the read at the // boundary instead. static void test_fdpass_boundary(void) { int sv[2], a[2], b[2], got = 0, fds[SCM_MAXFD], n, flags = 0; char tag = 0; struct msghdr m; struct iovec iv; char big[8]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0 || pipe(a) < 0 || pipe(b) < 0) { check(0, "sockets and pipes for the boundary test"); return; } sendfd(sv[1], a[0], '1'); sendfd(sv[1], b[0], '2'); // Ask for eight bytes. Two are available. memset_(&m, 0, sizeof(m)); iv.iov_base = big; iv.iov_len = sizeof(big); m.msg_iov = &iv; m.msg_iovlen = 1; n = recvmsg(sv[0], &m, 0); check(n == 1, "a read spanning two messages stops at the second's boundary"); n = recvfd(sv[0], &tag, fds, SCM_MAXFD, &got, &flags); check(n == 1 && tag == '2' && got == 1, "so the next read starts there, and gets that message's descriptor"); if (got == 1) close(fds[0]); close(sv[0]); close(sv[1]); close(a[0]); close(a[1]); close(b[0]); close(b[1]); } // A plain read(2) of a message carrying descriptors. There is nowhere to put // them, so they are closed -- the alternative is the kernel holding references // for a receiver that has proved it is not looking. static void test_fdpass_dropped(void) { int sv[2], p[2]; char c; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0 || pipe(p) < 0) { check(0, "sockets and a pipe for the dropped-descriptor test"); return; } sendfd(sv[1], p[1], 'D'); close(p[1]); // the kernel's reference is the last one check(read(sv[0], &c, 1) == 1 && c == 'D', "read(2) of a message carrying a descriptor returns its bytes"); check(pipe_at_eof(p[0]), "and closes the descriptor rather than holding it for nobody"); close(p[0]); close(sv[0]); close(sv[1]); } // A message with descriptors that is never received at all. The socket is // closed with them still queued, and the kernel holds the only references -- // so if close does not drain the queue they are unreachable and permanent. static void test_fdpass_orphaned(void) { int sv[2], p[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0 || pipe(p) < 0) { check(0, "sockets and a pipe for the orphaned-descriptor test"); return; } sendfd(sv[1], p[1], 'O'); close(p[1]); close(sv[0]); // never received close(sv[1]); check(pipe_at_eof(p[0]), "closing a socket closes the descriptors still queued on it"); close(p[0]); } // More descriptors than the receiver left room for. The ones that do not fit // are closed, and MSG_CTRUNC is how the caller is told it happened. static void test_fdpass_truncated(void) { int sv[2], a[2], b[2], got = 0, fds[SCM_MAXFD], flags = 0; char tag = 0; struct msghdr m; struct iovec iv; char cbuf[CMSG_SPACE(2 * sizeof(int))]; struct cmsghdr *c; int two[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0 || pipe(a) < 0 || pipe(b) < 0) { check(0, "sockets and pipes for the truncation test"); return; } // One message, two descriptors. memset_(&m, 0, sizeof(m)); memset_(cbuf, 0, sizeof(cbuf)); tag = 'T'; iv.iov_base = &tag; iv.iov_len = 1; m.msg_iov = &iv; m.msg_iovlen = 1; m.msg_control = cbuf; m.msg_controllen = sizeof(cbuf); c = (struct cmsghdr *)cbuf; c->cmsg_len = CMSG_LEN(2 * sizeof(int)); c->cmsg_level = SOL_SOCKET; c->cmsg_type = SCM_RIGHTS; two[0] = a[1]; two[1] = b[1]; memmove_(CMSG_DATA(c), two, sizeof(two)); check(sendmsg(sv[1], &m, 0) == 1, "sendmsg carries two descriptors at once"); close(a[1]); close(b[1]); // Receive with room for one. tag = 0; recvfd(sv[0], &tag, fds, 1, &got, &flags); check(got == 1, "a receiver with room for one gets one"); check((flags & MSG_CTRUNC) != 0, "and MSG_CTRUNC says the rest were dropped"); check(pipe_at_eof(b[0]) || pipe_at_eof(a[0]), "the descriptor that did not fit was closed, not leaked"); if (got == 1) close(fds[0]); close(a[0]); close(b[0]); close(sv[0]); close(sv[1]); } // Descriptors with no bytes to ride on. Delivery pins them to the position of // a first byte, so a message with no bytes has no position at which anyone // could ever be handed them; saying so beats accepting it and losing them. static void test_fdpass_nodata(void) { int sv[2], p[2]; struct msghdr m; char cbuf[CMSG_SPACE(sizeof(int))]; struct cmsghdr *c; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0 || pipe(p) < 0) { check(0, "sockets and a pipe for the no-data test"); return; } memset_(&m, 0, sizeof(m)); memset_(cbuf, 0, sizeof(cbuf)); m.msg_control = cbuf; m.msg_controllen = sizeof(cbuf); c = (struct cmsghdr *)cbuf; c->cmsg_len = CMSG_LEN(sizeof(int)); c->cmsg_level = SOL_SOCKET; c->cmsg_type = SCM_RIGHTS; memmove_(CMSG_DATA(c), &p[1], sizeof(int)); check(sendmsg(sv[1], &m, 0) < 0, "sendmsg refuses descriptors with no data to carry them"); close(p[1]); check(pipe_at_eof(p[0]), "and does not keep the reference it took while trying"); close(p[0]); close(sv[0]); close(sv[1]); } // --------------------------------------------------------------------------- // 15. A bound socket is a file. // // docs/architecture/kernel.md item 3, in its own words: bind() a socket, then // stat() and `ls -l` the path and see an `s`, then unlink() it. // // The reason it matters is not tidiness. A Wayland client finds its compositor // by bind()ing a real path and libwayland locks the file beside it; a name // table in the kernel can express neither. These checks are what a name table // cannot pass -- every one of them asks the *filesystem* about the socket, // not the socket layer. // --------------------------------------------------------------------------- static void test_fsnames(void) { struct sockaddr_un sa; struct stat st; int len, sv, c; puts_("a bound socket is a file:\n"); unlink("/tmp/socktest-fs"); // from a previous run sv = socket(AF_UNIX, SOCK_STREAM, 0); len = mkaddr(&sa, "/tmp/socktest-fs"); check(sv >= 0, "socket()"); check(bind(sv, (struct sockaddr *)&sa, len) == 0, "bind creates the path"); check(stat("/tmp/socktest-fs", &st) == 0, "and it can be stat()ed"); check(st.type == T_SOCK, "as a socket, not a regular file"); // Binding the same path twice is EADDRINUSE, and it has to come from the // directory -- a socket file left by a crashed server must refuse a // rebind for the same reason a running one does. { int s2 = socket(AF_UNIX, SOCK_STREAM, 0); check(s2 >= 0 && bind(s2, (struct sockaddr *)&sa, len) < 0, "a second bind to the same path is refused"); if (s2 >= 0) close(s2); } // Connecting to a path that is not a socket must be refused rather than // matched by name. A name table could not tell the difference. { struct sockaddr_un sb; int lb = mkaddr(&sb, "/tmp/socktest-notasock"); int fd = open("/tmp/socktest-notasock", O_CREATE | O_RDWR); if (fd >= 0) close(fd); c = socket(AF_UNIX, SOCK_STREAM, 0); check(c >= 0 && connect(c, (struct sockaddr *)&sb, lb) < 0, "connecting to a regular file is refused"); if (c >= 0) close(c); unlink("/tmp/socktest-notasock"); } check(unlink("/tmp/socktest-fs") == 0, "unlink removes it"); check(stat("/tmp/socktest-fs", &st) < 0, "and it is gone from the filesystem"); // The path is free again. On Linux the old socket keeps working for // anyone already connected; it is simply no longer findable by name. { int s3 = socket(AF_UNIX, SOCK_STREAM, 0); check(s3 >= 0 && bind(s3, (struct sockaddr *)&sa, len) == 0, "and the path is free to bind again"); if (s3 >= 0) close(s3); unlink("/tmp/socktest-fs"); } close(sv); } int main(void) { puts_("socktest: unix domain sockets\n"); test_roundtrip(); test_errors(); test_eof(); test_socketpair(); test_big(); test_sockopt(); test_nonblock(); test_fdpass(); test_fdpass_boundary(); test_fdpass_dropped(); test_fdpass_orphaned(); test_fdpass_truncated(); test_fdpass_nodata(); test_fsnames(); if (failures == 0) puts_("socktest: ALL CHECKS PASSED\n"); else { puts_("socktest: "); putn_(failures); puts_(" CHECK(S) FAILED\n"); } exit(); }