| /* | 
 |  * This file is part of the UCB release of Plan 9. It is subject to the license | 
 |  * terms in the LICENSE file found in the top-level directory of this | 
 |  * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No | 
 |  * part of the UCB release of Plan 9, including this file, may be copied, | 
 |  * modified, propagated, or distributed except according to the terms contained | 
 |  * in the LICENSE file. | 
 |  */ | 
 | #include <stdlib.h> | 
 |  | 
 | #include <fcntl.h> | 
 | #include <iplib/iplib.h> | 
 | #include <parlib/parlib.h> | 
 | #include <signal.h> | 
 | #include <stdio.h> | 
 | #include <unistd.h> | 
 |  | 
 | static int nettrans(char *, char *, int na, char *, int); | 
 |  | 
 | enum { | 
 | 	Maxpath = 256, | 
 | }; | 
 |  | 
 | /* Helper, given a net directory (translated from an addr/dialstring) by | 
 |  * nettrans), clones a conversation, returns the ctl, and optionally fills in | 
 |  * dir with the path of the directory (e.g. /net/tcp/1/). | 
 |  * | 
 |  * Returns the ctl FD, or -1 on error.  err_func is the function name to print | 
 |  * for error output. */ | 
 | static int __clone9(char *netdir, char *dir, char *err_func, int flags) | 
 | { | 
 | 	int ctl, n, m; | 
 | 	char buf[Maxpath]; | 
 | 	char *cp; | 
 |  | 
 | 	/* get a control channel */ | 
 | 	ctl = open(netdir, O_RDWR); | 
 | 	if (ctl < 0) { | 
 | 		fprintf(stderr, "%s opening %s: %r\n", err_func, netdir); | 
 | 		return -1; | 
 | 	} | 
 | 	cp = strrchr(netdir, '/'); | 
 | 	if (cp == NULL) { | 
 | 		fprintf(stderr, "%s arg format %s\n", err_func, netdir); | 
 | 		close(ctl); | 
 | 		return -1; | 
 | 	} | 
 | 	*cp = 0; | 
 |  | 
 | 	/* find out which line we have */ | 
 | 	n = snprintf(buf, sizeof(buf), "%s/", netdir); | 
 | 	m = read(ctl, &buf[n], sizeof(buf) - n - 1); | 
 | 	if (m <= 0) { | 
 | 		fprintf(stderr, "%s reading %s: %r\n", err_func, netdir); | 
 | 		close(ctl); | 
 | 		return -1; | 
 | 	} | 
 | 	buf[n + m] = 0; | 
 |  | 
 | 	/* return directory etc. */ | 
 | 	if (dir) { | 
 | 		strncpy(dir, buf, NETPATHLEN); | 
 | 		dir[NETPATHLEN - 1] = 0; | 
 | 	} | 
 | 	return ctl; | 
 | } | 
 |  | 
 | /* Clones a new network connection for a given dialstring (e.g. tcp!*!22). */ | 
 | int clone9(char *addr, char *dir, int flags) | 
 | { | 
 | 	char netdir[Maxpath]; | 
 | 	char naddr[Maxpath]; | 
 |  | 
 | 	if (nettrans(addr, naddr, sizeof(naddr), netdir, sizeof(netdir)) < 0) | 
 | 		return -1; | 
 | 	return __clone9(addr, dir, "clone", flags); | 
 | } | 
 |  | 
 | /* | 
 |  *  announce a network service. | 
 |  */ | 
 | int announce9(char *addr, char *dir, int flags) | 
 | { | 
 | 	int ctl, n; | 
 | 	char buf[Maxpath]; | 
 | 	char netdir[Maxpath]; | 
 | 	char naddr[Maxpath]; | 
 |  | 
 | 	/* | 
 | 	 *  translate the address | 
 | 	 */ | 
 | 	if (nettrans(addr, naddr, sizeof(naddr), netdir, sizeof(netdir)) < 0) | 
 | 		return -1; | 
 |  | 
 | 	ctl = __clone9(netdir, dir, "announce", flags); | 
 | 	if (ctl < 0) | 
 | 		return -1; | 
 |  | 
 | 	/* | 
 | 	 *  make the call | 
 | 	 */ | 
 | 	n = snprintf(buf, sizeof(buf), "announce %s", naddr); | 
 | 	if (write(ctl, buf, n) != n) { | 
 | 		fprintf(stderr, "announce writing %s: %r\n", netdir); | 
 | 		close(ctl); | 
 | 		return -1; | 
 | 	} | 
 |  | 
 | 	return ctl; | 
 | } | 
 |  | 
 | /* Gets a conversation and bypasses the protocol layer */ | 
 | int bypass9(char *addr, char *conv_dir, int flags) | 
 | { | 
 | 	int ctl, n; | 
 | 	char buf[Maxpath]; | 
 | 	char netdir[Maxpath]; | 
 | 	char naddr[Maxpath]; | 
 |  | 
 | 	if (nettrans(addr, naddr, sizeof(naddr), netdir, sizeof(netdir)) < 0) | 
 | 		return -1; | 
 | 	ctl = __clone9(netdir, conv_dir, "bypass", flags); | 
 | 	if (ctl < 0) | 
 | 		return -1; | 
 | 	n = snprintf(buf, sizeof(buf), "bypass %s", naddr); | 
 | 	if (write(ctl, buf, n) != n) { | 
 | 		fprintf(stderr, "bypass writing %s: %r\n", netdir); | 
 | 		close(ctl); | 
 | 		return -1; | 
 | 	} | 
 | 	return ctl; | 
 | } | 
 |  | 
 | /* | 
 |  *  listen for an incoming call | 
 |  */ | 
 | int listen9(char *dir, char *newdir, int flags) | 
 | { | 
 | 	int ctl, n, m; | 
 | 	char buf[Maxpath]; | 
 | 	char *cp; | 
 |  | 
 | 	/* | 
 | 	 *  open listen, wait for a call | 
 | 	 */ | 
 | 	snprintf(buf, sizeof(buf), "%s/listen", dir); | 
 | 	ctl = open(buf, O_RDWR | (flags & O_NONBLOCK)); | 
 | 	if (ctl < 0) { | 
 | 		if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) | 
 | 			fprintf(stderr, "listen opening %s: %r\n", buf); | 
 | 		return -1; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 *  find out which line we have | 
 | 	 */ | 
 | 	strncpy(buf, dir, sizeof(buf) - 1); | 
 | 	buf[sizeof(buf) - 1] = 0; | 
 | 	cp = strrchr(buf, '/'); | 
 | 	if (cp == NULL) { | 
 | 		close(ctl); | 
 | 		fprintf(stderr, "listen arg format %s\n", dir); | 
 | 		return -1; | 
 | 	} | 
 | 	*++cp = 0; | 
 | 	n = cp - buf; | 
 | 	m = read(ctl, cp, sizeof(buf) - n - 1); | 
 | 	if (m <= 0) { | 
 | 		close(ctl); | 
 | 		fprintf(stderr, "listen reading %s/listen: %r\n", dir); | 
 | 		return -1; | 
 | 	} | 
 | 	buf[n + m] = 0; | 
 |  | 
 | 	/* | 
 | 	 *  return directory etc. | 
 | 	 */ | 
 | 	if (newdir) { | 
 | 		strncpy(newdir, buf, NETPATHLEN); | 
 | 		newdir[NETPATHLEN - 1] = 0; | 
 | 	} | 
 | 	return ctl; | 
 | } | 
 |  | 
 | /* | 
 |  *  accept a call, return an fd to the open data file | 
 |  */ | 
 | int accept9(int ctl, char *dir) | 
 | { | 
 | 	char buf[Maxpath]; | 
 | 	char *num; | 
 | 	long n; | 
 |  | 
 | 	num = strrchr(dir, '/'); | 
 | 	if (num == NULL) | 
 | 		num = dir; | 
 | 	else | 
 | 		num++; | 
 |  | 
 | 	n = snprintf(buf, sizeof(buf), "accept %s", num); | 
 | 	/* ignore return value, network might not need accepts */ | 
 | 	write(ctl, buf, n); | 
 |  | 
 | 	snprintf(buf, sizeof(buf), "%s/data", dir); | 
 | 	return open(buf, O_RDWR); | 
 | } | 
 |  | 
 | /* | 
 |  *  reject a call, tell device the reason for the rejection | 
 |  */ | 
 | int reject9(int ctl, char *dir, char *cause) | 
 | { | 
 | 	char buf[Maxpath]; | 
 | 	char *num; | 
 | 	long n; | 
 |  | 
 | 	num = strrchr(dir, '/'); | 
 | 	if (num == 0) | 
 | 		num = dir; | 
 | 	else | 
 | 		num++; | 
 | 	snprintf(buf, sizeof(buf), "reject %s %s", num, cause); | 
 | 	n = strlen(buf); | 
 | 	if (write(ctl, buf, n) != n) | 
 | 		return -1; | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  *  perform the identity translation (in case we can't reach cs) | 
 |  */ | 
 | static int identtrans(char *netdir, char *addr, char *naddr, int na, char *file, | 
 |                       int nf) | 
 | { | 
 | 	char proto[Maxpath]; | 
 | 	char *p; | 
 |  | 
 | 	/* parse the protocol */ | 
 | 	strncpy(proto, addr, sizeof(proto)); | 
 | 	proto[sizeof(proto) - 1] = 0; | 
 | 	p = strchr(proto, '!'); | 
 | 	if (p) | 
 | 		*p++ = 0; | 
 |  | 
 | 	snprintf(file, nf, "%s/%s/clone", netdir, proto); | 
 | 	strncpy(naddr, p, na); | 
 | 	naddr[na - 1] = 0; | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | /* | 
 |  *  call up the connection server and get a translation | 
 |  */ | 
 | static int nettrans(char *addr, char *naddr, int na, char *file, int nf) | 
 | { | 
 | 	int i, fd; | 
 | 	char buf[Maxpath]; | 
 | 	char netdir[Maxpath]; | 
 | 	char *p, *p2; | 
 | 	long n; | 
 |  | 
 | 	/* | 
 | 	 *  parse, get network directory | 
 | 	 */ | 
 | 	p = strchr(addr, '!'); | 
 | 	if (p == 0) { | 
 | 		fprintf(stderr, "bad dial string: %s\n", addr); | 
 | 		return -1; | 
 | 	} | 
 | 	if (*addr != '/') { | 
 | 		strncpy(netdir, "/net", sizeof(netdir)); | 
 | 		netdir[sizeof(netdir) - 1] = 0; | 
 | 	} else { | 
 | 		for (p2 = p; *p2 != '/'; p2--) | 
 | 			; | 
 | 		i = p2 - addr; | 
 | 		if (i == 0 || i >= sizeof(netdir)) { | 
 | 			fprintf(stderr, "bad dial string: %s\n", addr); | 
 | 			return -1; | 
 | 		} | 
 | 		strncpy(netdir, addr, i); | 
 | 		netdir[i] = 0; | 
 | 		addr = p2 + 1; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 *  ask the connection server | 
 | 	 */ | 
 | 	snprintf(buf, sizeof(buf), "%s/cs", netdir); | 
 | 	fd = open(buf, O_RDWR); | 
 | 	if (fd < 0) | 
 | 		return identtrans(netdir, addr, naddr, na, file, nf); | 
 | 	if (write(fd, addr, strlen(addr)) < 0) { | 
 | 		close(fd); | 
 | 		return -1; | 
 | 	} | 
 | 	lseek(fd, 0, 0); | 
 | 	n = read(fd, buf, sizeof(buf) - 1); | 
 | 	close(fd); | 
 | 	if (n <= 0) | 
 | 		return -1; | 
 | 	buf[n] = 0; | 
 |  | 
 | 	/* | 
 | 	 *  parse the reply | 
 | 	 */ | 
 | 	p = strchr(buf, ' '); | 
 | 	if (p == 0) | 
 | 		return -1; | 
 | 	*p++ = 0; | 
 | 	strncpy(naddr, p, na); | 
 | 	naddr[na - 1] = 0; | 
 |  | 
 | 	if (buf[0] == '/') { | 
 | 		p = strchr(buf + 1, '/'); | 
 | 		if (p == NULL) | 
 | 			p = buf; | 
 | 		else | 
 | 			p++; | 
 | 	} | 
 | 	snprintf(file, nf, "%s/%s", netdir, p); | 
 | 	return 0; | 
 | } | 
 |  | 
 | int open_data_fd9(char *conv_dir, int flags) | 
 | { | 
 | 	char path_buf[MAX_PATH_LEN]; | 
 |  | 
 | 	snprintf(path_buf, sizeof(path_buf), "%s/data", conv_dir); | 
 | 	return open(path_buf, O_RDWR | flags); | 
 | } | 
 |  | 
 | /* Given a conversation directory, return the "remote" or "local" port, passed | 
 |  * as the string which.  Returns the port via *port and TRUE on success. */ | 
 | bool get_port9(char *conv_dir, char *which, uint16_t *port) | 
 | { | 
 | 	/* We don't have a MAX_DIALSTRING, but MAX_PATH_LEN should be enough. */ | 
 | 	char buf[MAX_PATH_LEN]; | 
 | 	int local_fd; | 
 | 	int ret; | 
 | 	char *p; | 
 |  | 
 | 	snprintf(buf, sizeof(buf), "%s/%s", conv_dir, which); | 
 | 	local_fd = open(buf, O_RDONLY); | 
 | 	if (local_fd < 0) | 
 | 		return FALSE; | 
 | 	ret = read(local_fd, buf, sizeof(buf)); | 
 | 	close(local_fd); | 
 | 	if (ret <= 0) | 
 | 		return FALSE; | 
 | 	p = strrchr(buf, '!'); | 
 | 	if (!p) | 
 | 		return FALSE; | 
 | 	p++; | 
 | 	*port = atoi(p); | 
 | 	return TRUE; | 
 | } |