/* url-socket.c - File namespace extensions. * **************************************************************** * Copyright (C) 1999, 2000 Thomas Lord * * See the file "COPYING" for further information about * the copyright and warranty status of this work. */ #include "hackerlab/os/alloca.h" #include "hackerlab/os/errno.h" #include "hackerlab/os/sys/param.h" #include "hackerlab/os/sys/socket.h" #include "hackerlab/bugs/panic.h" #include "hackerlab/char/char-class.h" #include "hackerlab/char/str.h" #include "hackerlab/mem/mem.h" #include "hackerlab/mem/must-malloc.h" #include "hackerlab/arrays/ar.h" #include "hackerlab/fmt/cvt.h" #include "hackerlab/fs/file-names.h" #include "hackerlab/vu/vu-sys.h" #include "hackerlab/vu/vu-bad-arg.h" #include "hackerlab/vu-network/url-socket.h" /************************************************************************ *(h0 "A VU Handler for the URL Socket Scheme") * * These functions provide VU namespace handlers for file-names * that designate various kinds of sockets. By using these handlers, * your programs can (almost) transparently handle filenames * like: * * inet://myserver.host.com:10000 * * or * * unix:/tmp/.X11-unix/X0 * * Two kinds of sockets are available: *server* sockets and *client* * sockets. * * "server sockets" are created with `socket(2)', `bind(2)', and * `listen(2)'. When `vu_open' is called, a call to `listen(2)' is * made and the client connection (if any) is returned. * * "client sockets" are created with `socket(2)', `bind(2)', and * `connect(2)'. * * Sockets may be named in either the `unix' or `inet' domains. * * The syntax of these URLs is described in the documentation for * `url_socket_push_client_handler' and * `url_socket_push_socket_handler'. */ /* struct url_socket_params * * The closure type for every file name pattern than can open a socket * URL. A pointer to one of these structures is used as the closure * when calling `vu_push_name_handler'. * * These closures are copied by must_malloc/mem_move. */ struct url_socket_params { enum url_socket_type type; /* What type of socket (server, client, both) is opened by this file name pattern? */ enum url_socket_domains domains; /* What domain (unix, inet, both) is opened by this file name pattern? */ int server_flags; /* How was the server opened? (O_NONBLOCK, etc.) */ int default_port; /* If the port isn't specified in the file name, this is the default. */ int backlog; /* For listen(2). */ url_socket_server_callback server_callback; /* Called back when a new server socket is created and bound. */ url_socket_connect_callback connection_callback; /* Called when a server socket receives a new connection. */ url_socket_server_close_callback server_close_callback; /* Called when a server socket is closed. */ void * closure; /* A closure for the two callbacks. */ }; /* url_socket_vtable * * System calls for functions that operate on descriptors, URL * handling for functions that operate on file names. */ static struct vu_fs_discipline url_socket_vtable; /* url_server_socket_vtable * * Errors for most functions. Server shut-down for `vu_close'. */ static struct vu_fs_discipline url_server_socket_vtable; /* __STDC__ prototypes for static functions */ static regex_t * url_socket_client_regex (t_uchar ** name, t_uchar *** doc, enum url_socket_domains domains, int default_port); static regex_t * url_socket_server_regex (t_uchar ** name, t_uchar *** doc, enum url_socket_domains domains, int may_be_client, int default_port); static regex_t * url_socket_only_server_regex (t_uchar ** name, t_uchar *** doc, enum url_socket_domains domains, int default_port); static int server_fd_for_addr (int * errn, struct sockaddr * addr, int addr_len, char * path, int flags, int mode, struct url_socket_params * params); static struct server_memo * find_memo_for_server_fd (int fd); static void remove_memo_for_server_fd (int fd); static int decode_socket_url (int * errn, struct sockaddr * addr, int * addr_len, struct url_socket_params * params, int * be_a_server, char * path, int be_a_server_hint); static void * url_socket_make_closure (void * closure); static void url_socket_free_closure (void * closure); static int url_socket_open (int * errn, char * path, int flags, int mode, void * closure); static int url_socket_close (int * errn, int fd, void * closure); static int url_socket_access (int * errn, char * path, int mode, void * closure); static int url_socket_chdir (int * errn, char * path, void * closure); static int url_socket_chmod (int * errn, char * path, int mode, void * closure); static int url_socket_chown (int * errn, char * path, int owner, int group, void * closure); static int url_socket_chroot (int * errn, char * path, void * closure); static int url_socket_closedir (int * errn, DIR * dir, void * closure); static int url_socket_fchdir (int * errn, int fd, void * closure); static int url_socket_fchmod (int * errn, int fd, int mode, void * closure); static int url_socket_fchown (int * errn, int fd, int owner, int group, void * closure); static int url_socket_fstat (int * errn, int fd, struct stat * buf, void * closure); static int url_socket_fsync (int * errn, int fd, void * closure); static int url_socket_ftruncate (int * errn, int fd, off_t where, void * closure); static int url_socket_link (int * errn, char * from, char * to, void * closure); static off_t url_socket_lseek (int * errn, int fd, off_t offset, int whence, void * closure); static int url_socket_lstat (int * errn, char * path, struct stat * buf, void * closure); static int url_socket_mkdir (int * errn, char * path, int mode, void * closure); static int url_socket_opendir (int * errn, DIR ** retv, char * path, void * closure); static ssize_t url_socket_read (int * errn, int fd, char * buf, size_t count, void * closure); static int url_socket_readdir (int * errn, struct alloc_limits * limits, char ** file_ret, DIR * dir, void * closure); static int url_socket_readlink (int * errn, char * path, char * buf, int bufsize, void * closure); static int url_socket_rename (int * errn, char * from, char * to, void * closure); static int url_socket_rmdir (int * errn, char * path, void * closure); static int url_socket_stat (int * errn, char * path, struct stat * buf, void * closure); static int url_socket_symlink (int * errn, char * from, char * to, void * closure); static int url_socket_truncate (int * errn, char * path, off_t where, void * closure); static int url_socket_unlink (int * errn, char * path, void * closure); static int url_socket_utime (int * errn, char * path, struct utimbuf * times, void * closure); static ssize_t url_socket_write (int * errn, int fd, char const * buf, size_t count, void * closure); static int url_socket_fcntl (int * errn, int fd, int cmd, long arg, void * closure); static int url_socket_dup (int * errn, int fd, void * closure); static int url_socket_dup2 (int * errn, int fd, int newfd, void * closure); static int url_socket_move_state (int * errn, int fd, int newfd, void * closure); static int url_server_socket_close (int * errn, int fd, void * closure); /* static regex_t * url_socket_client_regex (t_uchar * name, * t_uchar *** doc, * enum url_socket_domains domains, * int default_port); * * Return a file name regexp and namespace option name for client * sockets in the indicated domains. * * Domains: Regexp: Name: * ----------------------------------------------------------- * unix unix: client/unix * inet inet:// client/inet * both unix:\|inet:// client/any */ static regex_t * url_socket_client_regex (t_uchar ** name, t_uchar *** doc, enum url_socket_domains domains, int default_port) { switch (domains) { default: panic ("illegal domains specification to url_socket_push_client_handler"); case url_socket_unix: { static int initialized = 0; static regex_t regex; static t_uchar * docstr[] = { "unix:$PATH", "Client connection to a unix-domain socket.", "$PATH is ~-expanded.", 0 }; if (!initialized) { if (0 > regcomp (®ex, "^unix:", 0)) panic ("unable to compile socket url regexp"); initialized = 1; } *name = "client/unix"; *doc = docstr; return ®ex; } case url_socket_inet: { static int initialized = 0; static regex_t regex; static t_uchar * docstr[] = { "inet:$HOST:$PORT", "Client connection to an internet-domain", "socket.", 0 }; static t_uchar * docstr_opt_port[] = { "inet:$HOST[:$PORT]", "Client connection to an internet-domain", "socket.", 0 }; if (!initialized) { if (0 > regcomp (®ex, "^inet://", 0)) panic ("unable to compile socket url regexp"); initialized = 1; } *name = "client/inet"; *doc = ((default_port < 0) ? docstr : docstr_opt_port); return ®ex; } case url_socket_inet_or_unix: { static int initialized = 0; static regex_t regex; static t_uchar * docstr[] = { "unix:$PATH or inet:$HOST:$PORT", "Client connection to a unix or", "internet domain socket. $PATH", "is ~-expanded.", 0 }; static t_uchar * docstr_opt_port[] = { "unix:$PATH or inet:$HOST[:$PORT]", "Client connection to a unix or", "internet domain socket. $PATH", "is ~-expanded.", 0 }; if (!initialized) { if (0 > regcomp (®ex, "^[[:(unix:\\|inet://):]]", 0)) panic ("unable to compile socket url regexp"); initialized = 1; } *name = "client/any"; *doc = ((default_port < 0) ? docstr : docstr_opt_port); return ®ex; } } } /* static regex_t * url_socket_server_regex (t_uchar ** name, * t_uchar *** doc, * enum url_socket_domains domains, * int may_be_client, * int default_port); * * Return a file name regexp and option argument name for server * sockets in the indicated domains. Regexps that can also be used * for client sockets are included. * * Domains: Regexp: * -------------------------------------- * unix unix:\|unix-server: * inet inet://\|inet-server:// * both unix:\|unix-server:\|inet://\|inet-server:// * * See the comment for `url_socket_push_socket_handler' for the names * associated with each combination of `domains' and `may_be_client'. */ static regex_t * url_socket_server_regex (t_uchar ** name, t_uchar *** doc, enum url_socket_domains domains, int may_be_client, int default_port) { switch (domains) { default: panic ("illegal domains specification to url_socket_push_client_handler"); case url_socket_unix: { static int initialized = 0; static regex_t regex; if (!initialized) { if (0 > regcomp (®ex, "^[[:(unix:\\|unix-server:):]]", 0)) panic ("unable to compile socket url regexp"); initialized = 1; } if (may_be_client) { static t_uchar * docstr[] = { "unix:$PATH or unix-server:$PATH", "Be a server or client for a unix-domain", "socket. $PATH is ~-expanded.", 0 }; *name = "socket/unix"; *doc = docstr; } else { static t_uchar * docstr[] = { "unix:$PATH or unix-server:$PATH", "Be a server for a unix-domain socket.", "$PATH is ~-expanded.", 0 }; *name = "server/unix"; *doc = docstr; } return ®ex; } case url_socket_inet: { static int initialized = 0; static regex_t regex; if (!initialized) { if (0 > regcomp (®ex, "^[[:(inet://\\|inet-server://):]]", 0)) panic ("unable to compile socket url regexp"); initialized = 1; } if (may_be_client) { static t_uchar * docstr[] = { "inet:$HOST:$PORT", " or inet-server:$PATH:$PORT", "Be a server or client for an", "internet-domain socket.", 0 }; static t_uchar * docstr_opt_port[] = { "inet:$HOST[:$PORT]", " or inet-server:$PATH[:$PORT]", "Be a server or client for an", "internet-domain socket.", 0 }; *name = "socket/inet"; *doc = ((default_port < 0) ? docstr : docstr_opt_port); } else { static t_uchar * docstr[] = { "inet:$HOST:$PORT", " or inet-server:$PATH:$PORT", "Be a server for an internet-domain", "socket.", 0 }; static t_uchar * docstr_opt_port[] = { "inet:$HOST[:$PORT]", " or inet-server:$PATH[:$PORT]", "Be a server for an internet-domain", "socket.", 0 }; *name = "server/inet"; *doc = ((default_port < 0) ? docstr : docstr_opt_port); } return ®ex; } case url_socket_inet_or_unix: { static int initialized = 0; static regex_t regex; if (!initialized) { if (0 > regcomp (®ex, "^[[:(unix:\\|unix-server:\\|inet://\\|inet-server://):]]", 0)) panic ("unable to compile socket url regexp"); initialized = 1; } if (may_be_client) { static t_uchar * docstr[] = { "unix:$PATH, unix-server:$PATH", "inet:$HOST:$PORT", "or inet-server:$HOST:$PORT", "Be a client or server for a unix or", "internet domain socket. $PATH is", "~-expanded.", 0 }; static t_uchar * docstr_opt_port[] = { "unix:$PATH, unix-server:$PATH", "inet:$HOST:[$PORT]", "or inet-server:$HOST:[$PORT]", "Be client or a server for a unix or internet", "domain socket. $PATH is ~-expanded.", 0 }; *name = "socket/any"; *doc = ((default_port < 0) ? docstr : docstr_opt_port); } else { static t_uchar * docstr[] = { "unix:$PATH, unix-server:$PATH", "inet:$HOST:$PORT", "or inet-server:$HOST:$PORT", "Be a server for a unix or internet", "internet domain socket. $PATH is", "~-expanded.", 0 }; static t_uchar * docstr_opt_port[] = { "unix:$PATH, unix-server:$PATH", "inet:$HOST:[$PORT]", "or inet-server:$HOST:[$PORT]", "Be server for a unix or internet domain", "socket. $PATH is ~-expanded.", 0 }; *name = "server/any"; *doc = ((default_port < 0) ? docstr : docstr_opt_port); } return ®ex; } } } /* static regex_t * url_socket_only_server_regex (t_uchar ** name, * t_uchar *** doc, * enum url_socket_domains domains, * int default_port); * * Return a file name regexp and option argument name for server * sockets in the indicated domains. Regexps that can also be used * for client sockets are excluded. * * Domains: Regexp: * -------------------------------------- * unix unix-server: * inet inet-server:// * both unix-server:\|inet-server:// * * See the comment for `url_socket_push_socket_handler' for the names * associated with each domain. */ static regex_t * url_socket_only_server_regex (t_uchar ** name, t_uchar *** doc, enum url_socket_domains domains, int default_port) { switch (domains) { default: panic ("illegal domains specification to url_socket_push_client_handler"); case url_socket_unix: { static int initialized = 0; static regex_t regex; static t_uchar * docstr[] = { "unix-server:$PATH", "Be a server for a unix-domain socket.", "$PATH is ~-expanded.", 0 }; if (!initialized) { if (0 > regcomp (®ex, "^unix-server:", 0)) panic ("unable to compile socket url regexp"); initialized = 1; } *name = "socket/unix-server"; *doc = docstr; return ®ex; } case url_socket_inet: { static int initialized = 0; static regex_t regex; static t_uchar * docstr[] = { "inet-server:$PATH:$PORT", "Be a server for an internet-domain", "socket.", 0 }; static t_uchar * docstr_opt_port[] = { "inet-server:$PATH[:$PORT]", "Be a server for an internet-domain", "socket.", 0 }; if (!initialized) { if (0 > regcomp (®ex, "^inet-server://", 0)) panic ("unable to compile socket url regexp"); initialized = 1; } *name = "socket/inet-server"; *doc = ((default_port < 0) ? docstr : docstr_opt_port); return ®ex; } case url_socket_inet_or_unix: { static int initialized = 0; static regex_t regex; static t_uchar * docstr[] = { "unix-server:$PATH", "or inet-server:$HOST:$PORT", "Be a server for a unix or internet", "internet domain socket. $PATH is", "~-expanded.", 0 }; static t_uchar * docstr_opt_port[] = { "unix-server:$PATH", "or inet-server:$HOST:[$PORT]", "Be server for a unix or internet domain", "socket. $PATH is ~-expanded.", 0 }; if (!initialized) { if (0 > regcomp (®ex, "^[[:(unix-server:\\|inet-server://):]]", 0)) panic ("unable to compile socket url regexp"); initialized = 1; } *name = "socket/any-server"; *doc = ((default_port < 0) ? docstr : docstr_opt_port); return ®ex; } } } /*(c url_socket_push_client_handler) * void url_socket_push_client_handler (enum url_socket_domains domains, * int default_port, * int is_optional); * * Push a VU namespace handler for client sockets. * * Domains: File Name: * ------------------------------------------------------------------ * url_socket_unix unix:$PATH * url_socket_inet inet://$HOST[:$PORT] * url_socket_inet_or_unix unix:$PATH or inet://$HOST[:$PORT] * * Note that the `$PATH' of a unix domain socket is subject to "~" expansion * using `file_name_tilde_expand'. * * (See xref:"file_name_tilde_expand".) * * `default_port' is used for internet-domain sockets. * * If `is_optional' is 0, the handler is immediately added to the VU namespace. * If `is_optional' is not 0, the handler is added to the list of optional * handlers and may be enabled by `vu_enable_optional_name_handler'. * * (See xref:"vu_enable_optional_name_handler".) * * * Domains: Optional Handler Name: * ------------------------------------------------------------------ * url_socket_unix client/unix * url_socket_inet client/inet * url_socket_inet_or_unix client/any * */ void url_socket_push_client_handler (enum url_socket_domains domains, int default_port, int is_optional) { regex_t * regex; struct url_socket_params params; t_uchar * name; t_uchar ** doc; regex = url_socket_client_regex (&name, &doc, domains, default_port); params.type = url_socket_client; params.domains = domains; params.default_port = default_port; params.backlog = 0; params.server_callback = 0; params.connection_callback = 0; params.server_close_callback = 0; vu_push_name_handler (name, doc, regex, 0, &url_socket_vtable, (void *)¶ms, is_optional); } /*(c url_socket_push_socket_handler) * void url_socket_push_socket_handler * (enum url_socket_domains domains, * int server_flags, * int may_be_client, * int only_server_url, * int default_port, * int backlog, * url_socket_server_callback server_callback, * url_socket_connect_callback connection_callback, * url_socket_server_close_callback server_close_callback, * void * closure, * is_optional); * * Push a VU namespace handler for server sockets. After this call, a * call to `vu_open' with a suitable file name will work by creating a * socket. * * `server_flags' may be any bitwise combination of: * * O_NONBLOCK # Don't block waiting for connections. * * There are two kinds of file name patterns: those that can be used * to name both clients and servers, and those that can only be used * to name servers: * * server only: client or server: * ------------------------------------------------- * unix-server:$PATH unix:$PATH * inet-server://$HOST[:$PORT] inet://$HOST[:$PORT] * * By default, both kinds of URL are accepted and interpreted as names * of server sockets. Note that: * * `$PATH' of a unix domain socket is subject to "~" expansion using * `file_name_tilde_expand'. * * `$HOST' of an internet-domain socket may be `INADDR_LOOPBACK'. * If the URL is the name of a server, `$HOST' may also be * `INADDR_ANY'. (See `inet(4)'.) * * `$PORT' of an internet-domain server may be 0, meaning that the * system should assign a port number which will be reported in * the server callback in the `server_addr' parameter (see below). * * If `may_be_client' is not 0, then accept both kinds of URLs, but * interpret "client or server" URLs as naming client sockets (your * process will open a connection to some other server). * * If `only_server_url' is not 0, then accept "server only" URLs, but * not "client or server" URLs. In this case, `may_be_client' is * ignored. This is especially useful in combination with * `url_socket_push_client_handler' and other calls to * `vu_push_name_handler'. * * `default_port' is used when creating an internet-domain socket for * which the port number was not specified in the file-name. * * `backlog' is used for the call to `listen(2)'. * * `server_callback' is called when a new server socket has been * created successfully. It's arguments are explained below. * * `connect_callback' is called when a new connection has been * received. It's arguments are explained below. * * `server_close_callback' is called when the server descriptor * is closed. It's arguments are explained below. * * `closure' is an opaque value passed to the callback functions. * * If `is_optional' is 0, the handler is immediately added to the VU namespace. * If `is_optional' is non-0, the handler is added to the list of optional * handlers and may be enabled by `vu_enable_optional_name_handler'. * * domain(s) only_server_url may_be_client Optional Handler Name * -------------------------------------------------------------------- * unix 0 0 server/unix * inet 0 0 server/inet * inet_or_unix 0 0 server/any * unix 0 1 socket/unix * inet 0 1 socket/inet * inet_or_unix 0 1 socket/any * unix 1 * socket/unix-server * inet 1 * socket/inet-server * inet_or_unix 1 * socket/any-server * * * \Calling Conventions for Callbacks/ * * typedef void (*url_socket_server_callback) (char * path, * int server_flags, * int flags, * int mode, * int server_fd, * struct sockaddr * server_addr, * int server_addr_len, * void * closure); * * `path' is the file name that caused the server socket to * be created. * * `server_flags' is 0 or a bit-wise combination of `O_NONBLOCK'. * * `flags' is the `flags' parameter passed to `vu_open', if this * server socket was created by a call to `vu_open', 0 otherwise. * * `mode' is the `mode' parameter passed to `vu_open', if this * server socket was created by a call to `vu_open', 0 otherwise. * * `server_fd' is the descriptor number of the server socket. * * `server_addr' and `server_addr_len' is the address passed to * `bind'. * * `closure' is the `closure' argument passed to * `url_socket_push_socket_handler'. * * typedef void (*url_socket_connect_callback) * (char * path, * int flags, * int mode, * int server_fd, * int connection_fd, * struct sockaddr * client_addr, * int client_addr_len, * void * closure); * * `path' is the file name that caused the client connection to this * server to be created. * * `flags' is the `flags' parameter passed to `vu_open'. * * `mode' is the `mode' parameter passed to `vu_open'. * * `server_fd' is the descriptor number of the server socket. * * `connection_fd' is the descriptor number of the client connection. * * `client_addr' and `client_addr_len' is the address from `accept'. * * `closure' is the `closure' argument passed to * `url_socket_push_socket_handler'. * * typedef void (*url_socket_server_close_callback) * (int server_fd, * struct sockaddr * server_addr, * int server_addr_len, * void * closure); * * `server_fd' is the descriptor number of the server socket. * * `server_addr' and `server_addr_len' is the address passed to * `bind'. * * `closure' is the `closure' argument passed to * `url_socket_push_socket_handler'. * */ void url_socket_push_server_handler (enum url_socket_domains domains, int server_flags, int may_be_client, int only_server_url, int default_port, int backlog, url_socket_server_callback server_callback, url_socket_connect_callback connection_callback, url_socket_server_close_callback server_close_callback, void * closure, int is_optional) { regex_t * regex; struct url_socket_params params; t_uchar * name; t_uchar ** doc; if (only_server_url) regex = url_socket_only_server_regex (&name, &doc, domains, default_port); else regex = url_socket_server_regex (&name, &doc, domains, may_be_client, default_port); if (may_be_client) params.type = url_socket_server_or_client; else params.type = url_socket_server; params.domains = domains; params.server_flags = server_flags; params.default_port = default_port; params.backlog = backlog; params.server_callback = server_callback; params.connection_callback = connection_callback; params.server_close_callback = server_close_callback; params.closure = closure; vu_push_name_handler (name, doc, regex, 0, &url_socket_vtable, (void *)¶ms, is_optional); } /**************************************************************** * Caching Server Sockets * * The system creates only one server socket per address. */ #ifndef SOCK_MAXADDRLEN #undef MAX #define MAX(A,B) (((A)>=(B))?(A):(B)) #define SOCK_MAXADDRLEN MAX(sizeof(struct sockaddr_un), sizeof (struct sockaddr_in)) #endif struct server_memo { char addr_data[SOCK_MAXADDRLEN]; /* The address of the server */ int fd; /* The descriptor of the server */ int flags; /* O_NONBLOCK */ char * path; /* If this is a unix domain socket, the file name to unlink */ }; AR_TYPEDEF (struct server_memo, server_memo); static union { struct server_memo * smp; void * void_ptr; } addr_memo = {0}; #define IN_ADDR(A) ((struct sockaddr_in *)A) #define IN_ADDR(A) ((struct sockaddr_in *)A) #define UN_ADDR(A) ((struct sockaddr_un *)A) #define SOCKADDR(A) ((struct sockaddr *)(A)) #define ADDR_MEMO(N) SOCKADDR(addr_memo.smp[(N)].addr_data) #define IN_ADDR_MEMO(N) IN_ADDR(addr_memo.smp[(N)].addr_data) #define UN_ADDR_MEMO(N) UN_ADDR(addr_memo.smp[(N)].addr_data) /* static int server_fd_for_addr (int * errn, * struct sockaddr * addr, * int addr_len, * char * path, * int flags, * int mode, * struct url_socket_params * params); * * Return a server socket for a given address or -1. * * `errn' returns an errno code. * * `addr' and `addr_len' is the address of the server (for `bind(2)'). * * `path' is the `path' parameter for the server callback in `params'. * * `flags' is the `flags' parameter for the server callback in `params'. * * `mode' is the `mode' parameter for the server callback in `params'. * * `params' is the VU closure containing the server callback and other * parameters. */ static int server_fd_for_addr (int * errn, struct sockaddr * addr, int addr_len, char * path, int flags, int mode, struct url_socket_params * params) { int fd; int size; int x; int desired_flags; struct server_memo * memo; size = ar_size (addr_memo.void_ptr); for (x = 0; x < size; ++x) { if (addr->sa_family == ADDR_MEMO(x)->sa_family) { switch (addr->sa_family) { default: panic ("corrupt address memo in url-socket.c"); case AF_INET: if ( (IN_ADDR (addr)->sin_port == IN_ADDR_MEMO (x)->sin_port) && !mem_cmp ((t_uchar *)&IN_ADDR (addr)->sin_addr, (t_uchar *)&IN_ADDR_MEMO (x)->sin_addr, sizeof (struct in_addr))) { memo = &addr_memo.smp [x]; fd = addr_memo.smp[x].fd; goto got_fd; } break; case AF_UNIX: if (!str_cmp (UN_ADDR (addr)->sun_path, UN_ADDR_MEMO (x)->sun_path)) { memo = &addr_memo.smp [x]; fd = addr_memo.smp[x].fd; goto got_fd; } break; } } } fd = socket (addr->sa_family, SOCK_STREAM, 0); if (fd < 0) { *errn = errno; return -1; } { int ign; if (0 > setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (char *)&ign, sizeof (ign))) { *errn = errno; close (fd); return -1; } } if (0 > bind (fd, addr, addr_len)) { *errn = errno; close (fd); return -1; } if (0 > listen (fd, params->backlog)) { *errn = errno; close (fd); return -1; } { int orig_flags; struct server_memo temp; orig_flags = fcntl (fd, F_GETFL, 0); if (orig_flags < 0) { *errn = errno; close (fd); return -1; } temp.fd = fd; temp.flags = orig_flags & O_NONBLOCK; if (addr->sa_family == AF_INET) temp.path = 0; else temp.path = str_save (lim_use_must_malloc, UN_ADDR (addr)->sun_path); memo = ar_push_server_memo (&addr_memo.smp, temp); mem_move (memo->addr_data, (t_uchar *)addr, addr_len); } vu_set_fd_handler (fd, &url_server_socket_vtable, (void *)params); if (params->server_callback) params->server_callback (path, params->server_flags, flags, mode, fd, addr, addr_len, params->closure); got_fd: desired_flags = (params->server_flags & O_NONBLOCK); if (desired_flags != memo->flags) { if (-1 == fcntl (fd, F_SETFL, desired_flags)) { *errn = errno; return -1; } memo->flags = desired_flags; } return fd; } /* static void remove_memo_for_server_fd (int fd); * * Remove the cache of data about server socket `fd'. */ static struct server_memo * find_memo_for_server_fd (int fd) { int size; int x; size = ar_size (addr_memo.void_ptr); for (x = 0; x < size; ++x) { if (addr_memo.smp[x].fd == fd) return &addr_memo.smp[x]; } while (1) panic ("url-socket.c: trying to find a server fd memo for unknown server"); } /* static void remove_memo_for_server_fd (int fd); * * Remove the cache of data about server socket `fd'. */ static void remove_memo_for_server_fd (int fd) { int size; int x; size = ar_size (addr_memo.void_ptr); for (x = 0; x < size; ++x) { if (addr_memo.smp[x].fd == fd) { addr_memo.smp[x] = addr_memo.smp[size - 1]; ar_pop_server_memo (&addr_memo.smp); return; } } panic ("url-socket.c: trying to remove server fd memo for unknown server"); } /* static int decode_socket_url (int * errn, * struct sockaddr * addr, * int * addr_len, * int * be_a_server, * char * path, * struct url_socket_params * params, * int be_a_server_hint); * * Translate a URL to a socket address. Return 0 on success, * -1 in the event of an error. * * `errn' returns an errno code. * * `addr' and `addr_len' return the translated address. * * `be_a_server' returns 0 if a client socket was specified, * 1 if a server socket was specified. The `path' specifies a * server if the URL scheme is "unix-server" or "inet-server", * or if the `type' field of `params' is `url_socket_server'. * It is an error (EINVAL) if the `type' field of `params' is * `url_socket_client', but the URL scheme is "unix-server" or * "inet-server" (Other kinds of errors may also return EINVAL.) * * `path' is the URL to translate. * * `params' is the URL params associated with `path'. For example, this * is the `closure' parameter passed to `url_socket_open'. * * `be_a_server_hint' applies only if the `type' field of `params' * is `url_socket_server_or_client'. * */ static int decode_socket_url (int * errn, struct sockaddr * addr, int * addr_len, struct url_socket_params * params, int * be_a_server, char * path, int be_a_server_hint) { int is_server_address; is_server_address = 0; if (!str_cmp_prefix ("inet://", path) || (is_server_address = !str_cmp_prefix ("inet-server://", path))) { char * addr_spec; char * end_of_addr_spec; char * host; /* Sanity check the requested socket type against the * parameters for this handler. * * Perhaps this should actually panic. */ if ( (params->domains != url_socket_inet) && (params->domains != url_socket_inet_or_unix)) { bogus_addr: *errn = EINVAL; return -1; } IN_ADDR(addr)->sin_family = AF_INET; /* Find the extent of the host part of the path. * Find the port number, if any was specified. */ addr_spec = str_chr_index (path, '/'); addr_spec += 2; end_of_addr_spec = str_chr_index (addr_spec, ':'); if (!end_of_addr_spec) { /* No port specified in path. Use the default. */ end_of_addr_spec = str_chr_index (addr_spec, 0); if (params->default_port < 0) goto bogus_addr; IN_ADDR(addr)->sin_port = htons (params->default_port); } else { char * port_name; unsigned long port; int errn; /* Port number explicitly specified. */ port_name = end_of_addr_spec + 1; if (0 > cvt_decimal_to_ulong (&errn, &port, port_name, str_length (port_name))) return -1; IN_ADDR(addr)->sin_port = htons (port); } /* Make a copy of the hostname, so we can have a 0-terminated * form. */ host = alloca (1 + end_of_addr_spec - addr_spec); str_cpy_n (host, addr_spec, end_of_addr_spec - addr_spec); host[end_of_addr_spec - addr_spec] = 0; /* Does it appear to be an IP address? */ if (char_is_digit (*host)) { IN_ADDR(addr)->sin_addr.s_addr = inet_addr (host); if (IN_ADDR(addr)->sin_addr.s_addr == -1) goto bogus_addr; } #if 0 /* INADDR_LOOPBACK is not defined in some versions of BSD/OS. */ else if (!str_cmp ("INADDR_LOOPBACK", host)) { IN_ADDR(addr)->sin_addr.s_addr = INADDR_LOOPBACK; } #endif else if (!str_cmp ("INADDR_ANY", host)) { IN_ADDR(addr)->sin_addr.s_addr = INADDR_ANY; } else { struct hostent * hostentp; hostentp = gethostbyname (host); if (!hostentp || (hostentp->h_addrtype != AF_INET)) goto bogus_addr; mem_move ((t_uchar *)&IN_ADDR(addr)->sin_addr, hostentp->h_addr, hostentp->h_length); } *addr_len = sizeof (struct sockaddr_in); } else if (!str_cmp_prefix ("unix:", path) || (is_server_address = !str_cmp_prefix ("unix-server:", path))) { char * socket_path; char * expanded_socket_path; int len; /* Sanity check the requested socket type against the * parameters for this handler. */ if ( (params->domains != url_socket_unix) && (params->domains != url_socket_inet_or_unix)) goto bogus_addr; UN_ADDR(addr)->sun_family = AF_UNIX; socket_path = 1 + str_chr_index (path, ':'); expanded_socket_path = file_name_tilde_expand (lim_use_must_malloc, socket_path); len = str_length (expanded_socket_path); if (len + 1 > sizeof (UN_ADDR(addr)->sun_path)) { *errn = ENAMETOOLONG; return -1; } str_cpy (UN_ADDR(addr)->sun_path, expanded_socket_path); *addr_len = sizeof (struct sockaddr_un); } if (is_server_address && (params->type == url_socket_client)) goto bogus_addr; *be_a_server = ( is_server_address || (params->type == url_socket_server) || be_a_server_hint); if ( (addr->sa_family == AF_INET) && !*be_a_server && (IN_ADDR(addr)->sin_addr.s_addr == INADDR_ANY)) { *errn = EINVAL; return -1; } return 0; } /*c int url_socket_create_server_socket (int * errn, char * path) * * Create a server socket for `path'. Return a descriptor number * or -1. * * `errno' returns an errno code. * * `path' should be a URL matching a pattern added to the VU file * namespace. * * `vu_close' can be used to close a server fd. */ int url_socket_create_server_socket (int * errn, char * path) { struct vu_handler * handler; struct url_socket_params * params; char addr_space[SOCK_MAXADDRLEN]; struct sockaddr * addr; int addr_len; int be_a_server; handler = vu_path_dispatch (path); params = (struct url_socket_params *)handler->closure; addr = (struct sockaddr *)addr_space; if (0 > decode_socket_url (errn, addr, &addr_len, params, &be_a_server, path, 0)) return -1; if (!be_a_server) { *errn = EINVAL; return -1; } else { int server_fd; server_fd = server_fd_for_addr (errn, addr, addr_len, path, 0, 0, params); return server_fd; } } int url_inet_client (int * errn, t_uchar * host, int port) { t_ulong host_addr; if (char_is_digit (*host)) { struct in_addr a; a.s_addr = inet_addr (host); if (a.s_addr == -1) { bogus_addr: *errn = EINVAL; return -1; } host_addr = ntohl (a.s_addr); } else { struct hostent * hostentp; hostentp = gethostbyname (host); if (!hostentp || (hostentp->h_addrtype != AF_INET)) goto bogus_addr; mem_move ((t_uchar *)&host_addr, hostentp->h_addr, hostentp->h_length); host_addr = ntohl (host_addr); } return url_inet_client_addr (errn, host_addr, port); } int url_inet_client_addr (int * errn, t_ulong host, int port) { int fd; struct url_socket_params params; struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons (port); addr.sin_addr.s_addr = htonl (host); fd = socket (AF_INET, SOCK_STREAM, 0); if (fd < 0) { *errn = errno; return -1; } if (0 > connect (fd, (struct sockaddr *)&addr, sizeof (addr))) { *errn = errno; close (fd); return -1; } params.type = url_socket_client; params.domains = url_socket_inet; params.default_port = 0; params.backlog = 5; params.server_callback = 0; params.connection_callback = 0; params.server_close_callback = 0; vu_set_fd_handler (fd, &url_socket_vtable, (void *)¶ms); return fd; } int url_inet_server_accept (int * errn, int server_fd) { struct url_socket_params params; char client_addr_space[SOCK_MAXADDRLEN]; struct sockaddr * client_addr; int client_addr_len; int connection_fd; client_addr = (struct sockaddr *)client_addr_space; client_addr_len = sizeof (client_addr_space); connection_fd = accept (server_fd, client_addr, &client_addr_len); if (connection_fd < 0) { *errn = errno; return -1; } params.type = url_socket_server; params.domains = url_socket_inet; params.default_port = 0; params.backlog = 0; params.server_callback = 0; params.connection_callback = 0; params.server_close_callback = 0; vu_set_fd_handler (connection_fd, &url_socket_vtable, (void *)¶ms); return connection_fd; } int url_inet_server (alloc_limits limits, t_ulong * host_addr_is, t_uchar ** host_id_is, int * port_is, int * errn, t_uchar * host, int port) { int fd; struct url_socket_params params; struct sockaddr_in addr; t_uchar myhost[MAXHOSTNAMELEN + 1]; mem_set0 ((t_uchar *)&addr, sizeof (addr)); addr.sin_family = AF_INET; addr.sin_port = htons (port); if (!host) { if (host_id_is) { if (0 > gethostname (myhost, sizeof (myhost))) { *errn = errno; return -1; } *host_id_is = str_save (limits, myhost); } addr.sin_addr.s_addr = htonl (INADDR_ANY); } else { if (host_id_is) *host_id_is = str_save (limits, host); if (char_is_digit (*host)) { addr.sin_addr.s_addr = inet_addr (host); if (addr.sin_addr.s_addr == -1) goto bogus_addr; } else if (!str_cmp ("INADDR_ANY", host)) { addr.sin_addr.s_addr = htonl (INADDR_ANY); } else { struct hostent * hostentp; hostentp = gethostbyname (host); if (!hostentp || (hostentp->h_addrtype != AF_INET)) { bogus_addr: *errn = EINVAL; return -1; } mem_move ((t_uchar *)&addr.sin_addr, hostentp->h_addr, hostentp->h_length); } } mem_set0 ((t_uchar *)¶ms, sizeof (params)); params.type = url_socket_server; params.domains = url_socket_inet; params.default_port = 0; params.backlog = 1; params.server_callback = 0; params.connection_callback = 0; params.server_close_callback = 0; fd = server_fd_for_addr (errn, (struct sockaddr *)&addr, sizeof (addr), 0, O_RDWR, 0, ¶ms); if (fd < 0) { return -1; } else { if (host_addr_is) { if (ntohl (addr.sin_addr.s_addr) != INADDR_ANY) { *host_addr_is = ntohl (addr.sin_addr.s_addr); } else { char my_name[MAXHOSTNAMELEN + 1]; struct hostent * hostent; if (0 > gethostname (my_name, sizeof (my_name))) { int ign; *errn = errno; vu_close (&ign, fd); return -1; } hostent = gethostbyname (my_name); if (!hostent) { int ign; *errn = errno; vu_close (&ign, fd); return -1; } { t_uint32 tmp; mem_move ((t_uchar *)&tmp, (t_uchar *)hostent->h_addr, 4); *host_addr_is = ntohl (tmp); } } } if (port_is) { char name_space[SOCK_MAXADDRLEN]; int name_len; struct sockaddr * name; name = (struct sockaddr *)name_space; name_len = sizeof (name_space); if (0 > getsockname (fd, name, &name_len)) { int e; *errn = errno; vu_close (&e, fd); return -1; } else { if (port_is) { *port_is = ntohs (IN_ADDR(name)->sin_port); } } } } return fd; } /**************************************************************** * VU `open' and `close' for Sockets * * */ /*c void * url_socket_make_closure (void * closure); * * A `make_closure' function for a VU vtable. (See `vu_push_name_handler'.) */ static void * url_socket_make_closure (void * closure) { struct url_socket_params * answer; answer = (struct url_socket_params *)must_malloc (sizeof (*answer)); *answer = *(struct url_socket_params *)closure; return (void *)answer; } /*c void url_socket_free_closure (void * closure); * * A `free_closure' function for a VU vtable. (See `vu_push_name_handler'.) */ static void url_socket_free_closure (void * closure) { must_free (closure); } /*c void url_socket_open (int * errn, * char * path, * int flags, * int mode, * void * closure); * * An `open' function for a VU vtable. (See `vu_push_name_handler'.) * * See `url_socket_push_client_handler' and `url_socket_push_server_handler'. */ static int url_socket_open (int * errn, char * path, int flags, int mode, void * closure) { struct url_socket_params * params; char addr_space[SOCK_MAXADDRLEN]; struct sockaddr * addr; int addr_len; int be_a_server; params = (struct url_socket_params *)closure; addr = (struct sockaddr *)addr_space; if (0 > decode_socket_url (errn, addr, &addr_len, params, &be_a_server, path, 0)) return -1; if (!be_a_server) { int fd; fd = socket (addr->sa_family, SOCK_STREAM, 0); if (fd < 0) { *errn = errno; return -1; } if (0 > connect (fd, addr, addr_len)) { *errn = errno; close (fd); return -1; } return fd; } else { char client_addr_space[SOCK_MAXADDRLEN]; struct sockaddr * client_addr; int client_addr_len; int server_fd; int connection_fd; client_addr = (struct sockaddr *)client_addr_space; client_addr_len = sizeof (client_addr_space); server_fd = server_fd_for_addr (errn, addr, addr_len, path, flags, mode, params); if (server_fd < 0) return -1; connection_fd = accept (server_fd, client_addr, &client_addr_len); if (connection_fd < 0) { *errn = errno; return -1; } if (params->connection_callback) params->connection_callback (path, flags, mode, server_fd, connection_fd, client_addr, client_addr_len, params->closure); return connection_fd; } } /*c void url_socket_close (int * errn, int fd, void * closure); * char * path, * int flags, * int mode, * void * closure); * * A `close' function for a VU vtable. (See `vu_push_name_handler'.) * * See `url_socket_push_client_handler' and `url_socket_push_server_handler'. */ static int url_socket_close (int * errn, int fd, void * closure) { int x; int size; size = ar_size (addr_memo.void_ptr); for (x = 0; x < size; ++x) if (fd == addr_memo.smp [x].fd) { if (0 > vu_sys_close (errn, fd, 0)) return -1; addr_memo.smp[x] = addr_memo.smp[size - 1]; ar_pop_server_memo (&addr_memo.smp); return 0; } return vu_sys_close (errn, fd, 0); } /**************************************************************** * VU Vtable Functions for URL Sockets */ static int url_socket_access (int * errn, char * path, int mode, void * closure) { *errn = EINVAL; return -1; } static int url_socket_chdir (int * errn, char * path, void * closure) { *errn = EINVAL; return -1; } static int url_socket_chmod (int * errn, char * path, int mode, void * closure) { *errn = EINVAL; return -1; } static int url_socket_chown (int * errn, char * path, int owner, int group, void * closure) { *errn = EINVAL; return -1; } static int url_socket_chroot (int * errn, char * path, void * closure) { *errn = EINVAL; return -1; } static int url_socket_closedir (int * errn, DIR * dir, void * closure) { *errn = EINVAL; return -1; } static int url_socket_fchdir (int * errn, int fd, void * closure) { *errn = EINVAL; return -1; } static int url_socket_fchmod (int * errn, int fd, int mode, void * closure) { *errn = EINVAL; return -1; } static int url_socket_fchown (int * errn, int fd, int owner, int group, void * closure) { *errn = EINVAL; return -1; } static int url_socket_fstat (int * errn, int fd, struct stat * buf, void * closure) { *errn = EINVAL; return -1; } static int url_socket_fsync (int * errn, int fd, void * closure) { *errn = EINVAL; return -1; } static int url_socket_ftruncate (int * errn, int fd, off_t where, void * closure) { *errn = EINVAL; return -1; } static int url_socket_link (int * errn, char * from, char * to, void * closure) { *errn = EINVAL; return -1; } static off_t url_socket_lseek (int * errn, int fd, off_t offset, int whence, void * closure) { *errn = ESPIPE; return -1; } static int url_socket_lstat (int * errn, char * path, struct stat * buf, void * closure) { *errn = EINVAL; return -1; } static int url_socket_mkdir (int * errn, char * path, int mode, void * closure) { *errn = EINVAL; return -1; } static int url_socket_opendir (int * errn, DIR ** retv, char * path, void * closure) { *errn = EINVAL; return -1; } static ssize_t url_socket_read (int * errn, int fd, char * buf, size_t count, void * closure) { return vu_sys_read (errn, fd, buf, count, 0); } static int url_socket_readdir (int * errn, struct alloc_limits * limits, char ** file_ret, DIR * dir, void * closure) { *errn = EINVAL; return -1; } static int url_socket_readlink (int * errn, char * path, char * buf, int bufsize, void * closure) { *errn = EINVAL; return -1; } static int url_socket_rename (int * errn, char * from, char * to, void * closure) { *errn = EINVAL; return -1; } static int url_socket_rmdir (int * errn, char * path, void * closure) { *errn = EINVAL; return -1; } static int url_socket_stat (int * errn, char * path, struct stat * buf, void * closure) { *errn = EINVAL; return -1; } static int url_socket_symlink (int * errn, char * from, char * to, void * closure) { *errn = EINVAL; return -1; } static int url_socket_truncate (int * errn, char * path, off_t where, void * closure) { *errn = EINVAL; return -1; } static int url_socket_unlink (int * errn, char * path, void * closure) { *errn = EINVAL; return -1; } static int url_socket_utime (int * errn, char * path, struct utimbuf * times, void * closure) { *errn = EINVAL; return -1; } static ssize_t url_socket_write (int * errn, int fd, char const * buf, size_t count, void * closure) { return vu_sys_write (errn, fd, buf, count, 0); } static int url_socket_fcntl (int * errn, int fd, int cmd, long arg, void * closure) { return vu_sys_fcntl (errn, fd, cmd, arg, 0); } static int url_socket_dup (int * errn, int fd, void * closure) { return vu_sys_dup (errn, fd, 0); } static int url_socket_dup2 (int * errn, int fd, int newfd, void * closure) { return vu_sys_dup2 (errn, fd, newfd, 0); } static int url_socket_move_state (int * errn, int fd, int newfd, void * closure) { if (fd == newfd) return fd; vu_set_fd_handler (newfd, &url_socket_vtable, closure); return 0; } static struct vu_fs_discipline url_socket_vtable = { VU_FS_DISCIPLINE_INITIALIZERS (url_socket_) }; /**************************************************************** * VU Vtable Functions for Server Sockets */ /*c int url_server_socket_close (int * errn, int fd, void * closure); * * A `vu_close_fn' implementation. * * Close a descriptor which is a server-socket created by * `url_socket_open' or `url_socket_create_server_socket'. * */ static int url_server_socket_close (int * errn, int fd, void * closure) { struct url_socket_params * params; struct server_memo * memo; struct sockaddr * addr; int addr_len; char addr_space[SOCK_MAXADDRLEN]; params = (struct url_socket_params *)closure; memo = find_memo_for_server_fd (fd); if (close (fd) < 0) { *errn = errno; return -1; } if (memo->path) { int errn2; vu_unlink (&errn2, memo->path); } addr = (struct sockaddr *)memo->addr_data; addr_len = (addr->sa_family ? sizeof (struct sockaddr_in) : sizeof (struct sockaddr_un)); mem_move (addr_space, (t_uchar *)addr, addr_len); addr = (struct sockaddr *)addr_space; remove_memo_for_server_fd (fd); if (params->server_close_callback) params->server_close_callback (fd, addr, addr_len, params->closure); return 0; } #define url_server_socket_make_closure url_socket_make_closure #define url_server_socket_free_closure url_socket_free_closure #define url_server_socket_access (vu_access_fn)vu_bad_arg #define url_server_socket_chdir (vu_chdir_fn)vu_bad_arg #define url_server_socket_chmod (vu_chmod_fn)vu_bad_arg #define url_server_socket_chown (vu_chown_fn)vu_bad_arg #define url_server_socket_chroot (vu_chroot_fn)vu_bad_arg #define url_server_socket_closedir (vu_closedir_fn)vu_bad_arg #define url_server_socket_fchdir (vu_fchdir_fn)vu_bad_arg #define url_server_socket_fchmod (vu_fchmod_fn)vu_bad_arg #define url_server_socket_fchown (vu_fchown_fn)vu_bad_arg #define url_server_socket_fstat (vu_fstat_fn)vu_bad_arg #define url_server_socket_fsync (vu_fsync_fn)vu_bad_arg #define url_server_socket_ftruncate (vu_ftruncate_fn)vu_bad_arg #define url_server_socket_link (vu_link_fn)vu_bad_arg #define url_server_socket_lseek (vu_lseek_fn)vu_bad_arg #define url_server_socket_lstat (vu_lstat_fn)vu_bad_arg #define url_server_socket_mkdir (vu_mkdir_fn)vu_bad_arg #define url_server_socket_open (vu_open_fn)vu_bad_arg #define url_server_socket_opendir (vu_opendir_fn)vu_bad_arg #define url_server_socket_read (vu_read_fn)vu_bad_arg #define url_server_socket_readdir (vu_readdir_fn)vu_bad_arg #define url_server_socket_readlink (vu_readlink_fn)vu_bad_arg #define url_server_socket_rename (vu_rename_fn)vu_bad_arg #define url_server_socket_rmdir (vu_rmdir_fn)vu_bad_arg #define url_server_socket_stat (vu_stat_fn)vu_bad_arg #define url_server_socket_symlink (vu_symlink_fn)vu_bad_arg #define url_server_socket_truncate (vu_truncate_fn)vu_bad_arg #define url_server_socket_unlink (vu_unlink_fn)vu_bad_arg #define url_server_socket_utime (vu_utime_fn)vu_bad_arg #define url_server_socket_write (vu_write_fn)vu_bad_arg #define url_server_socket_fcntl (vu_fcntl_fn)vu_bad_arg #define url_server_socket_dup (vu_dup_fn)vu_bad_arg #define url_server_socket_dup2 (vu_dup2_fn)vu_bad_arg #define url_server_socket_move_state (vu_dup2_fn)vu_bad_arg static struct vu_fs_discipline url_server_socket_vtable = { VU_FS_DISCIPLINE_INITIALIZERS (url_server_socket_) };