summaryrefslogtreecommitdiff
path: root/ext/socket/unixsocket.c
diff options
context:
space:
mode:
authorakr <akr@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2009-01-17 04:11:27 +0000
committerakr <akr@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2009-01-17 04:11:27 +0000
commit97cbab78dcc43f696077d3bbce1ee35f9d947339 (patch)
tree7570d3897cbfe9d7b45c077dbf4b760b246243cb /ext/socket/unixsocket.c
parent8ae8afa649e7f59bfddf60dcb9fb844c02b3dc95 (diff)
* ext/socket: split files for each class.
* ext/socket/rubysocket.h: common header. * ext/socket/basicsocket.c: new file for BasicSocket. * ext/socket/ipsocket.c: new file for IPSocket. * ext/socket/tcpsocket.c: new file for TCPSocket. * ext/socket/tcpserver.c: new file for TCPServer. * ext/socket/sockssocket.c: new file for SOCKSSocket. * ext/socket/udpsocket.c: new file for UDPSocket. * ext/socket/unixsocket.c: new file for UNIXSocket. * ext/socket/unixserver.c: new file for UNIXServer. * ext/socket/socket.c: now for Socket. * ext/socket/raddrinfo.c: new file for AddrInfo and name resolution. * ext/socket/constants.c: new file for constants. * ext/socket/init.c: new file for utilities. * ext/socket/mkconstants.rb: export *_to_int. * ext/socket/extconf.rb: add new object files. * ext/socket/depend: add dependencies for new files. * ext/.document: add new files. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@21619 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'ext/socket/unixsocket.c')
-rw-r--r--ext/socket/unixsocket.c499
1 files changed, 499 insertions, 0 deletions
diff --git a/ext/socket/unixsocket.c b/ext/socket/unixsocket.c
new file mode 100644
index 0000000..7039a82
--- /dev/null
+++ b/ext/socket/unixsocket.c
@@ -0,0 +1,499 @@
+/************************************************
+
+ unixsocket.c -
+
+ created at: Thu Mar 31 12:21:29 JST 1994
+
+ Copyright (C) 1993-2007 Yukihiro Matsumoto
+
+************************************************/
+
+#include "rubysocket.h"
+
+#ifdef HAVE_SYS_UN_H
+struct unixsock_arg {
+ struct sockaddr_un *sockaddr;
+ int fd;
+};
+
+static VALUE
+unixsock_connect_internal(struct unixsock_arg *arg)
+{
+ return (VALUE)ruby_connect(arg->fd, (struct sockaddr*)arg->sockaddr,
+ sizeof(*arg->sockaddr), 0);
+}
+
+VALUE
+init_unixsock(VALUE sock, VALUE path, int server)
+{
+ struct sockaddr_un sockaddr;
+ int fd, status;
+ rb_io_t *fptr;
+
+ SafeStringValue(path);
+ fd = ruby_socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ rb_sys_fail("socket(2)");
+ }
+
+ MEMZERO(&sockaddr, struct sockaddr_un, 1);
+ sockaddr.sun_family = AF_UNIX;
+ if (sizeof(sockaddr.sun_path) <= RSTRING_LEN(path)) {
+ rb_raise(rb_eArgError, "too long unix socket path (max: %dbytes)",
+ (int)sizeof(sockaddr.sun_path)-1);
+ }
+ memcpy(sockaddr.sun_path, RSTRING_PTR(path), RSTRING_LEN(path));
+
+ if (server) {
+ status = bind(fd, (struct sockaddr*)&sockaddr, sizeof(sockaddr));
+ }
+ else {
+ int prot;
+ struct unixsock_arg arg;
+ arg.sockaddr = &sockaddr;
+ arg.fd = fd;
+ status = rb_protect((VALUE(*)(VALUE))unixsock_connect_internal,
+ (VALUE)&arg, &prot);
+ if (prot) {
+ close(fd);
+ rb_jump_tag(prot);
+ }
+ }
+
+ if (status < 0) {
+ close(fd);
+ rb_sys_fail(sockaddr.sun_path);
+ }
+
+ if (server) listen(fd, 5);
+
+ init_sock(sock, fd);
+ if (server) {
+ GetOpenFile(sock, fptr);
+ fptr->pathv = rb_str_new_frozen(path);
+ }
+
+ return sock;
+}
+#endif
+
+#ifdef HAVE_SYS_UN_H
+/*
+ * call-seq:
+ * UNIXSocket.new(path) => unixsocket
+ *
+ * Creates a new UNIX client socket connected to _path_.
+ *
+ * s = UNIXSocket.new("/tmp/sock")
+ * s.send "hello", 0
+ *
+ */
+static VALUE
+unix_init(VALUE sock, VALUE path)
+{
+ return init_unixsock(sock, path, 0);
+}
+
+/*
+ * call-seq:
+ * unixsocket.path => path
+ *
+ * Returns the path of the local address of unixsocket.
+ *
+ * s = UNIXServer.new("/tmp/sock")
+ * p s.path #=> "/tmp/sock"
+ *
+ */
+static VALUE
+unix_path(VALUE sock)
+{
+ rb_io_t *fptr;
+
+ GetOpenFile(sock, fptr);
+ if (NIL_P(fptr->pathv)) {
+ struct sockaddr_un addr;
+ socklen_t len = sizeof(addr);
+ if (getsockname(fptr->fd, (struct sockaddr*)&addr, &len) < 0)
+ rb_sys_fail(0);
+ fptr->pathv = rb_obj_freeze(rb_str_new_cstr(unixpath(&addr, len)));
+ }
+ return rb_str_dup(fptr->pathv);
+}
+
+/*
+ * call-seq:
+ * unixsocket.recvfrom(maxlen [, flags]) => [mesg, unixaddress]
+ *
+ * Receives a message via _unixsocket_.
+ *
+ * _maxlen_ is the maximum number of bytes to receive.
+ *
+ * _flags_ should be a bitwise OR of Socket::MSG_* constants.
+ *
+ * s1 = Socket.new(:UNIX, :DGRAM, 0)
+ * s1_ai = AddrInfo.unix("/tmp/sock1")
+ * s1.bind(s1_ai)
+ *
+ * s2 = Socket.new(:UNIX, :DGRAM, 0)
+ * s2_ai = AddrInfo.unix("/tmp/sock2")
+ * s2.bind(s2_ai)
+ * s3 = UNIXSocket.for_fd(s2.fileno)
+ *
+ * s1.send "a", 0, s2_ai
+ * p s3.recvfrom(10) #=> ["a", ["AF_UNIX", "/tmp/sock1"]]
+ *
+ */
+static VALUE
+unix_recvfrom(int argc, VALUE *argv, VALUE sock)
+{
+ return s_recvfrom(sock, argc, argv, RECV_UNIX);
+}
+
+#if defined(HAVE_ST_MSG_CONTROL) && defined(SCM_RIGHTS)
+#define FD_PASSING_BY_MSG_CONTROL 1
+#else
+#define FD_PASSING_BY_MSG_CONTROL 0
+#endif
+
+#if defined(HAVE_ST_MSG_ACCRIGHTS)
+#define FD_PASSING_BY_MSG_ACCRIGHTS 1
+#else
+#define FD_PASSING_BY_MSG_ACCRIGHTS 0
+#endif
+
+struct iomsg_arg {
+ int fd;
+ struct msghdr msg;
+};
+
+static VALUE
+sendmsg_blocking(void *data)
+{
+ struct iomsg_arg *arg = data;
+ return sendmsg(arg->fd, &arg->msg, 0);
+}
+
+/*
+ * call-seq:
+ * unixsocket.send_io(io) => nil
+ *
+ * Sends _io_ as file descriptor passing.
+ *
+ * s1, s2 = UNIXSocket.pair
+ *
+ * s1.send_io STDOUT
+ * stdout = s2.recv_io
+ *
+ * p STDOUT.fileno #=> 1
+ * p stdout.fileno #=> 6
+ *
+ * stdout.puts "hello" # outputs "hello\n" to standard output.
+ */
+static VALUE
+unix_send_io(VALUE sock, VALUE val)
+{
+#if defined(HAVE_SENDMSG) && (FD_PASSING_BY_MSG_CONTROL || FD_PASSING_BY_MSG_ACCRIGHTS)
+ int fd;
+ rb_io_t *fptr;
+ struct iomsg_arg arg;
+ struct iovec vec[1];
+ char buf[1];
+
+#if FD_PASSING_BY_MSG_CONTROL
+ struct {
+ struct cmsghdr hdr;
+ char pad[8+sizeof(int)+8];
+ } cmsg;
+#endif
+
+ if (rb_obj_is_kind_of(val, rb_cIO)) {
+ rb_io_t *valfptr;
+ GetOpenFile(val, valfptr);
+ fd = valfptr->fd;
+ }
+ else if (FIXNUM_P(val)) {
+ fd = FIX2INT(val);
+ }
+ else {
+ rb_raise(rb_eTypeError, "neither IO nor file descriptor");
+ }
+
+ GetOpenFile(sock, fptr);
+
+ arg.msg.msg_name = NULL;
+ arg.msg.msg_namelen = 0;
+
+ /* Linux and Solaris doesn't work if msg_iov is NULL. */
+ buf[0] = '\0';
+ vec[0].iov_base = buf;
+ vec[0].iov_len = 1;
+ arg.msg.msg_iov = vec;
+ arg.msg.msg_iovlen = 1;
+
+#if FD_PASSING_BY_MSG_CONTROL
+ arg.msg.msg_control = (caddr_t)&cmsg;
+ arg.msg.msg_controllen = CMSG_LEN(sizeof(int));
+ arg.msg.msg_flags = 0;
+ MEMZERO((char*)&cmsg, char, sizeof(cmsg));
+ cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int));
+ cmsg.hdr.cmsg_level = SOL_SOCKET;
+ cmsg.hdr.cmsg_type = SCM_RIGHTS;
+ *(int *)CMSG_DATA(&cmsg.hdr) = fd;
+#else
+ arg.msg.msg_accrights = (caddr_t)&fd;
+ arg.msg.msg_accrightslen = sizeof(fd);
+#endif
+
+ arg.fd = fptr->fd;
+ rb_thread_fd_writable(arg.fd);
+ if ((int)BLOCKING_REGION(sendmsg_blocking, &arg) == -1)
+ rb_sys_fail("sendmsg(2)");
+
+ return Qnil;
+#else
+ rb_notimplement();
+ return Qnil; /* not reached */
+#endif
+}
+
+static VALUE
+recvmsg_blocking(void *data)
+{
+ struct iomsg_arg *arg = data;
+ return recvmsg(arg->fd, &arg->msg, 0);
+}
+
+/*
+ * call-seq:
+ * unixsocket.recv_io([klass [, mode]]) => io
+ *
+ * UNIXServer.open("/tmp/sock") {|serv|
+ * UNIXSocket.open("/tmp/sock") {|c|
+ * s = serv.accept
+ *
+ * c.send_io STDOUT
+ * stdout = s.recv_io
+ *
+ * p STDOUT.fileno #=> 1
+ * p stdout.fileno #=> 7
+ *
+ * stdout.puts "hello" # outputs "hello\n" to standard output.
+ * }
+ * }
+ *
+ */
+static VALUE
+unix_recv_io(int argc, VALUE *argv, VALUE sock)
+{
+#if defined(HAVE_RECVMSG) && (FD_PASSING_BY_MSG_CONTROL || FD_PASSING_BY_MSG_ACCRIGHTS)
+ VALUE klass, mode;
+ rb_io_t *fptr;
+ struct iomsg_arg arg;
+ struct iovec vec[2];
+ char buf[1];
+
+ int fd;
+#if FD_PASSING_BY_MSG_CONTROL
+ struct {
+ struct cmsghdr hdr;
+ char pad[8+sizeof(int)+8];
+ } cmsg;
+#endif
+
+ rb_scan_args(argc, argv, "02", &klass, &mode);
+ if (argc == 0)
+ klass = rb_cIO;
+ if (argc <= 1)
+ mode = Qnil;
+
+ GetOpenFile(sock, fptr);
+
+ arg.msg.msg_name = NULL;
+ arg.msg.msg_namelen = 0;
+
+ vec[0].iov_base = buf;
+ vec[0].iov_len = sizeof(buf);
+ arg.msg.msg_iov = vec;
+ arg.msg.msg_iovlen = 1;
+
+#if FD_PASSING_BY_MSG_CONTROL
+ arg.msg.msg_control = (caddr_t)&cmsg;
+ arg.msg.msg_controllen = CMSG_SPACE(sizeof(int));
+ arg.msg.msg_flags = 0;
+ cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int));
+ cmsg.hdr.cmsg_level = SOL_SOCKET;
+ cmsg.hdr.cmsg_type = SCM_RIGHTS;
+ *(int *)CMSG_DATA(&cmsg.hdr) = -1;
+#else
+ arg.msg.msg_accrights = (caddr_t)&fd;
+ arg.msg.msg_accrightslen = sizeof(fd);
+ fd = -1;
+#endif
+
+ arg.fd = fptr->fd;
+ rb_thread_wait_fd(arg.fd);
+ if ((int)BLOCKING_REGION(recvmsg_blocking, &arg) == -1)
+ rb_sys_fail("recvmsg(2)");
+
+#if FD_PASSING_BY_MSG_CONTROL
+ if (arg.msg.msg_controllen < CMSG_LEN(sizeof(int))) {
+ rb_raise(rb_eSocket,
+ "file descriptor was not passed (msg_controllen=%d smaller than CMSG_LEN(sizeof(int))=%d)",
+ (int)arg.msg.msg_controllen, (int)CMSG_LEN(sizeof(int)));
+ }
+ if (CMSG_SPACE(sizeof(int)) < arg.msg.msg_controllen) {
+ rb_raise(rb_eSocket,
+ "file descriptor was not passed (msg_controllen=%d bigger than CMSG_SPACE(sizeof(int))=%d)",
+ (int)arg.msg.msg_controllen, (int)CMSG_SPACE(sizeof(int)));
+ }
+ if (cmsg.hdr.cmsg_len != CMSG_LEN(sizeof(int))) {
+ rb_raise(rb_eSocket,
+ "file descriptor was not passed (cmsg_len=%d, %d expected)",
+ (int)cmsg.hdr.cmsg_len, (int)CMSG_LEN(sizeof(int)));
+ }
+ if (cmsg.hdr.cmsg_level != SOL_SOCKET) {
+ rb_raise(rb_eSocket,
+ "file descriptor was not passed (cmsg_level=%d, %d expected)",
+ cmsg.hdr.cmsg_level, SOL_SOCKET);
+ }
+ if (cmsg.hdr.cmsg_type != SCM_RIGHTS) {
+ rb_raise(rb_eSocket,
+ "file descriptor was not passed (cmsg_type=%d, %d expected)",
+ cmsg.hdr.cmsg_type, SCM_RIGHTS);
+ }
+#else
+ if (arg.msg.msg_accrightslen != sizeof(fd)) {
+ rb_raise(rb_eSocket,
+ "file descriptor was not passed (accrightslen) : %d != %d",
+ arg.msg.msg_accrightslen, (int)sizeof(fd));
+ }
+#endif
+
+#if FD_PASSING_BY_MSG_CONTROL
+ fd = *(int *)CMSG_DATA(&cmsg.hdr);
+#endif
+
+ if (klass == Qnil)
+ return INT2FIX(fd);
+ else {
+ ID for_fd;
+ int ff_argc;
+ VALUE ff_argv[2];
+ CONST_ID(for_fd, "for_fd");
+ ff_argc = mode == Qnil ? 1 : 2;
+ ff_argv[0] = INT2FIX(fd);
+ ff_argv[1] = mode;
+ return rb_funcall2(klass, for_fd, ff_argc, ff_argv);
+ }
+#else
+ rb_notimplement();
+ return Qnil; /* not reached */
+#endif
+}
+
+/*
+ * call-seq:
+ * unixsocket.addr => [address_family, unix_path]
+ *
+ * Returns the local address as an array which contains
+ * address_family and unix_path.
+ *
+ * Example
+ * serv = UNIXServer.new("/tmp/sock")
+ * p serv.addr #=> ["AF_UNIX", "/tmp/sock"]
+ */
+static VALUE
+unix_addr(VALUE sock)
+{
+ rb_io_t *fptr;
+ struct sockaddr_un addr;
+ socklen_t len = sizeof addr;
+
+ GetOpenFile(sock, fptr);
+
+ if (getsockname(fptr->fd, (struct sockaddr*)&addr, &len) < 0)
+ rb_sys_fail("getsockname(2)");
+ return unixaddr(&addr, len);
+}
+
+/*
+ * call-seq:
+ * unixsocket.peeraddr => [address_family, unix_path]
+ *
+ * Returns the remote address as an array which contains
+ * address_family and unix_path.
+ *
+ * Example
+ * serv = UNIXServer.new("/tmp/sock")
+ * c = UNIXSocket.new("/tmp/sock")
+ * p c.peeraddr #=> ["AF_UNIX", "/tmp/sock"]
+ */
+static VALUE
+unix_peeraddr(VALUE sock)
+{
+ rb_io_t *fptr;
+ struct sockaddr_un addr;
+ socklen_t len = sizeof addr;
+
+ GetOpenFile(sock, fptr);
+
+ if (getpeername(fptr->fd, (struct sockaddr*)&addr, &len) < 0)
+ rb_sys_fail("getpeername(2)");
+ return unixaddr(&addr, len);
+}
+#endif
+
+#ifdef HAVE_SYS_UN_H
+/*
+ * call-seq:
+ * UNIXSocket.pair([type [, protocol]]) => [unixsocket1, unixsocket2]
+ * UNIXSocket.socketpair([type [, protocol]]) => [unixsocket1, unixsocket2]
+ *
+ * Creates a pair of sockets connected each other.
+ *
+ * _socktype_ should be a socket type such as: :STREAM, :DGRAM, :RAW, etc.
+ *
+ * _protocol_ should be a protocol defined in the domain.
+ * 0 is default protocol for the domain.
+ *
+ * s1, s2 = UNIXSocket.pair
+ * s1.send "a", 0
+ * s1.send "b", 0
+ * p s2.recv(10) #=> "ab"
+ *
+ */
+static VALUE
+unix_s_socketpair(int argc, VALUE *argv, VALUE klass)
+{
+ VALUE domain, type, protocol;
+ domain = INT2FIX(PF_UNIX);
+
+ rb_scan_args(argc, argv, "02", &type, &protocol);
+ if (argc == 0)
+ type = INT2FIX(SOCK_STREAM);
+ if (argc <= 1)
+ protocol = INT2FIX(0);
+
+ return sock_s_socketpair(klass, domain, type, protocol);
+}
+#endif
+
+/*
+ * UNIXSocket class
+ */
+void
+Init_unixsocket(void)
+{
+#ifdef HAVE_SYS_UN_H
+ rb_cUNIXSocket = rb_define_class("UNIXSocket", rb_cBasicSocket);
+ rb_define_method(rb_cUNIXSocket, "initialize", unix_init, 1);
+ rb_define_method(rb_cUNIXSocket, "path", unix_path, 0);
+ rb_define_method(rb_cUNIXSocket, "addr", unix_addr, 0);
+ rb_define_method(rb_cUNIXSocket, "peeraddr", unix_peeraddr, 0);
+ rb_define_method(rb_cUNIXSocket, "recvfrom", unix_recvfrom, -1);
+ rb_define_method(rb_cUNIXSocket, "send_io", unix_send_io, 1);
+ rb_define_method(rb_cUNIXSocket, "recv_io", unix_recv_io, -1);
+ rb_define_singleton_method(rb_cUNIXSocket, "socketpair", unix_s_socketpair, -1);
+ rb_define_singleton_method(rb_cUNIXSocket, "pair", unix_s_socketpair, -1);
+#endif
+}