xref: /plan9/sys/src/libc/9sys/dial.c (revision 8ccc32ef0b9b7222ff49b683668eb07a18989e02)
1 /*
2  * dial - connect to a service (parallel version)
3  */
4 #include <u.h>
5 #include <libc.h>
6 
7 typedef struct Conn Conn;
8 typedef struct Dest Dest;
9 typedef struct DS DS;
10 
11 enum
12 {
13 	Maxstring	= 128,
14 	Maxpath		= 256,
15 
16 	Maxcsreply	= 64*80,	/* this is probably overly generous */
17 	/*
18 	 * this should be a plausible slight overestimate for non-interactive
19 	 * use even if it's ridiculously long for interactive use.
20 	 */
21 	Maxconnms	= 2*60*1000,	/* 2 minutes */
22 };
23 
24 struct DS {
25 	/* dist string */
26 	char	buf[Maxstring];
27 	char	*netdir;
28 	char	*proto;
29 	char	*rem;
30 
31 	/* other args */
32 	char	*local;
33 	char	*dir;
34 	int	*cfdp;
35 };
36 
37 /*
38  * malloc these; they need to be writable by this proc & all children.
39  * the stack is private to each proc, and static allocation in the data
40  * segment would not permit concurrent dials within a multi-process program.
41  */
42 struct Conn {
43 	int	pid;
44 	int	dead;
45 
46 	int	dfd;
47 	int	cfd;
48 	char	dir[NETPATHLEN+1];
49 	char	err[ERRMAX];
50 };
51 struct Dest {
52 	Conn	*conn;			/* allocated array */
53 	Conn	*connend;
54 	int	nkid;
55 
56 	long	oalarm;
57 	int	naddrs;
58 
59 	QLock	winlck;
60 	int	winner;			/* index into conn[] */
61 
62 	char	*nextaddr;
63 	char	addrlist[Maxcsreply];
64 };
65 
66 static int	call(char*, char*, DS*, Dest*, Conn*);
67 static int	csdial(DS*);
68 static void	_dial_string_parse(char*, DS*);
69 
70 
71 /*
72  *  the dialstring is of the form '[/net/]proto!dest'
73  */
74 static int
dialimpl(char * dest,char * local,char * dir,int * cfdp)75 dialimpl(char *dest, char *local, char *dir, int *cfdp)
76 {
77 	DS ds;
78 	int rv;
79 	char err[ERRMAX], alterr[ERRMAX];
80 
81 	ds.local = local;
82 	ds.dir = dir;
83 	ds.cfdp = cfdp;
84 
85 	_dial_string_parse(dest, &ds);
86 	if(ds.netdir)
87 		return csdial(&ds);
88 
89 	ds.netdir = "/net";
90 	rv = csdial(&ds);
91 	if(rv >= 0)
92 		return rv;
93 	err[0] = '\0';
94 	errstr(err, sizeof err);
95 	if(strstr(err, "refused") != 0){
96 		werrstr("%s", err);
97 		return rv;
98 	}
99 	ds.netdir = "/net.alt";
100 	rv = csdial(&ds);
101 	if(rv >= 0)
102 		return rv;
103 
104 	alterr[0] = 0;
105 	errstr(alterr, sizeof alterr);
106 	if(strstr(alterr, "translate") || strstr(alterr, "does not exist"))
107 		werrstr("%s", err);
108 	else
109 		werrstr("%s", alterr);
110 	return rv;
111 }
112 
113 /*
114  * the thread library can't cope with rfork(RFMEM|RFPROC),
115  * so it must override this with a private version of dial.
116  */
117 int (*_dial)(char *, char *, char *, int *) = dialimpl;
118 
119 int
dial(char * dest,char * local,char * dir,int * cfdp)120 dial(char *dest, char *local, char *dir, int *cfdp)
121 {
122 	return (*_dial)(dest, local, dir, cfdp);
123 }
124 
125 static int
connsalloc(Dest * dp,int addrs)126 connsalloc(Dest *dp, int addrs)
127 {
128 	Conn *conn;
129 
130 	free(dp->conn);
131 	dp->connend = nil;
132 	assert(addrs > 0);
133 
134 	dp->conn = mallocz(addrs * sizeof *dp->conn, 1);
135 	if(dp->conn == nil)
136 		return -1;
137 	dp->connend = dp->conn + addrs;
138 	for(conn = dp->conn; conn < dp->connend; conn++)
139 		conn->cfd = conn->dfd = -1;
140 	return 0;
141 }
142 
143 static void
freedest(Dest * dp)144 freedest(Dest *dp)
145 {
146 	long oalarm;
147 
148 	if (dp == nil)
149 		return;
150 	oalarm = dp->oalarm;
151 	free(dp->conn);
152 	free(dp);
153 	if (oalarm >= 0)
154 		alarm(oalarm);
155 }
156 
157 static void
closeopenfd(int * fdp)158 closeopenfd(int *fdp)
159 {
160 	if (*fdp >= 0) {
161 		close(*fdp);
162 		*fdp = -1;
163 	}
164 }
165 
166 static void
notedeath(Dest * dp,char * exitsts)167 notedeath(Dest *dp, char *exitsts)
168 {
169 	int i, n, pid;
170 	char *fields[5];			/* pid + 3 times + error */
171 	Conn *conn;
172 
173 	for (i = 0; i < nelem(fields); i++)
174 		fields[i] = "";
175 	n = tokenize(exitsts, fields, nelem(fields));
176 	if (n < 4)
177 		return;
178 	pid = atoi(fields[0]);
179 	if (pid <= 0)
180 		return;
181 	for (conn = dp->conn; conn < dp->connend; conn++)
182 		if (conn->pid == pid && !conn->dead) {  /* it's one we know? */
183 			if (conn - dp->conn != dp->winner) {
184 				closeopenfd(&conn->dfd);
185 				closeopenfd(&conn->cfd);
186 			}
187 			strncpy(conn->err, fields[4], sizeof conn->err - 1);
188 			conn->err[sizeof conn->err - 1] = '\0';
189 			conn->dead = 1;
190 			return;
191 		}
192 	/* not a proc that we forked */
193 }
194 
195 static int
outstandingprocs(Dest * dp)196 outstandingprocs(Dest *dp)
197 {
198 	Conn *conn;
199 
200 	for (conn = dp->conn; conn < dp->connend; conn++)
201 		if (!conn->dead)
202 			return 1;
203 	return 0;
204 }
205 
206 static int
reap(Dest * dp)207 reap(Dest *dp)
208 {
209 	char exitsts[2*ERRMAX];
210 
211 	if (outstandingprocs(dp) && await(exitsts, sizeof exitsts) >= 0) {
212 		notedeath(dp, exitsts);
213 		return 0;
214 	}
215 	return -1;
216 }
217 
218 static int
fillinds(DS * ds,Dest * dp)219 fillinds(DS *ds, Dest *dp)
220 {
221 	Conn *conn;
222 
223 	if (dp->winner < 0)
224 		return -1;
225 	conn = &dp->conn[dp->winner];
226 	if (ds->cfdp)
227 		*ds->cfdp = conn->cfd;
228 	if (ds->dir) {
229 		strncpy(ds->dir, conn->dir, NETPATHLEN);
230 		ds->dir[NETPATHLEN-1] = '\0';
231 	}
232 	return conn->dfd;
233 }
234 
235 static int
connectwait(Dest * dp,char * besterr)236 connectwait(Dest *dp, char *besterr)
237 {
238 	Conn *conn;
239 
240 	/* wait for a winner or all attempts to time out */
241 	while (dp->winner < 0 && reap(dp) >= 0)
242 		;
243 
244 	/* kill all of our still-live kids & reap them */
245 	for (conn = dp->conn; conn < dp->connend; conn++)
246 		if (!conn->dead)
247 			postnote(PNPROC, conn->pid, "alarm");
248 	while (reap(dp) >= 0)
249 		;
250 
251 	/* rummage about and report some error string */
252 	for (conn = dp->conn; conn < dp->connend; conn++)
253 		if (conn - dp->conn != dp->winner && conn->dead &&
254 		    conn->err[0]) {
255 			strncpy(besterr, conn->err, ERRMAX-1);
256 			besterr[ERRMAX-1] = '\0';
257 			break;
258 		}
259 	return dp->winner;
260 }
261 
262 static int
parsecs(Dest * dp,char ** clonep,char ** destp)263 parsecs(Dest *dp, char **clonep, char **destp)
264 {
265 	char *dest, *p;
266 
267 	dest = strchr(dp->nextaddr, ' ');
268 	if(dest == nil) {
269 		p = strchr(dp->nextaddr, '\n');
270 		if(p)
271 			*p = '\0';
272 		werrstr("malformed clone cmd from cs `%s'", dp->nextaddr);
273 		if(p)
274 			*p = '\n';
275 		return -1;
276 	}
277 	*dest++ = '\0';
278 	p = strchr(dest, '\n');
279 	if(p == nil)
280 		return -1;
281 	*p++ = '\0';
282 	*clonep = dp->nextaddr;
283 	*destp = dest;
284 	dp->nextaddr = p;		/* advance to next line */
285 	return 0;
286 }
287 
288 static void
pickuperr(char * besterr,char * err)289 pickuperr(char *besterr, char *err)
290 {
291 	err[0] = '\0';
292 	errstr(err, ERRMAX);
293 	if(strstr(err, "does not exist") == 0)
294 		strcpy(besterr, err);
295 }
296 
297 static int
catcher(void *,char * s)298 catcher(void *, char *s)
299 {
300 	return strstr(s, "alarm") != nil;
301 }
302 
303 /*
304  * try all addresses in parallel and take the first one that answers;
305  * this helps when systems have ip v4 and v6 addresses but are
306  * only reachable from here on one (or some) of them.
307  */
308 static int
dialmulti(DS * ds,Dest * dp)309 dialmulti(DS *ds, Dest *dp)
310 {
311 	int rv, kid, kidme;
312 	char *clone, *dest;
313 	char besterr[ERRMAX];
314 
315 	dp->winner = -1;
316 	dp->nkid = 0;
317 	while(dp->winner < 0 && *dp->nextaddr != '\0' &&
318 	    parsecs(dp, &clone, &dest) >= 0) {
319 		kidme = dp->nkid++;		/* make private copy on stack */
320 		kid = rfork(RFPROC|RFMEM);	/* spin off a call attempt */
321 		if (kid < 0)
322 			--dp->nkid;
323 		else if (kid == 0) {
324 			char err[ERRMAX];
325 
326 			/* only in kid, to avoid atnotify callbacks in parent */
327 			atnotify(catcher, 1);
328 
329 			*besterr = '\0';
330 			rv = call(clone, dest, ds, dp, &dp->conn[kidme]);
331 			if(rv < 0)
332 				pickuperr(besterr, err);
333 			_exits(besterr);	/* avoid atexit callbacks */
334 		}
335 	}
336 	*besterr = '\0';
337 	rv = connectwait(dp, besterr);
338 	if(rv < 0)
339 		werrstr("%s", (*besterr? besterr: "unknown error"));
340 	return rv;
341 }
342 
343 static int
csdial(DS * ds)344 csdial(DS *ds)
345 {
346 	int n, fd, rv, addrs, bleft;
347 	char c;
348 	char *addrp, *clone2, *dest;
349 	char buf[Maxstring], clone[Maxpath], err[ERRMAX], besterr[ERRMAX];
350 	Dest *dp;
351 
352 	werrstr("");
353 	dp = mallocz(sizeof *dp, 1);
354 	if(dp == nil)
355 		return -1;
356 	dp->winner = -1;
357 	dp->oalarm = alarm(0);
358 	if (connsalloc(dp, 1) < 0) {		/* room for a single conn. */
359 		freedest(dp);
360 		return -1;
361 	}
362 
363 	/*
364 	 *  open connection server
365 	 */
366 	snprint(buf, sizeof(buf), "%s/cs", ds->netdir);
367 	fd = open(buf, ORDWR);
368 	if(fd < 0){
369 		/* no connection server, don't translate */
370 		snprint(clone, sizeof(clone), "%s/%s/clone", ds->netdir, ds->proto);
371 		rv = call(clone, ds->rem, ds, dp, &dp->conn[0]);
372 		fillinds(ds, dp);
373 		freedest(dp);
374 		return rv;
375 	}
376 
377 	/*
378 	 *  ask connection server to translate
379 	 *  e.g., net!cs.bell-labs.com!smtp
380 	 */
381 	snprint(buf, sizeof(buf), "%s!%s", ds->proto, ds->rem);
382 	if(write(fd, buf, strlen(buf)) < 0){
383 		close(fd);
384 		freedest(dp);
385 		return -1;
386 	}
387 
388 	/*
389 	 *  read all addresses from the connection server:
390 	 *  /net/tcp/clone 135.104.9.78!25
391 	 *  /net/tcp/clone 2620:0:dc0:1805::29!25
392 	 *
393 	 *  assumes that we'll get one record per read.
394 	 */
395 	seek(fd, 0, 0);
396 	addrs = 0;
397 	addrp = dp->nextaddr = dp->addrlist;
398 	bleft = sizeof dp->addrlist - 2;	/* 2 is room for \n\0 */
399 	while(bleft > 0 && (n = read(fd, addrp, bleft)) > 0) {
400 		if (addrp[n-1] != '\n')
401 			addrp[n++] = '\n';
402 		addrs++;
403 		addrp += n;
404 		bleft -= n;
405 	}
406 	*addrp = '\0';
407 
408 	/*
409 	 * if we haven't read all of cs's output, assume the last line might
410 	 * have been truncated and ignore it.  we really don't expect this
411 	 * to happen.
412 	 */
413 	if (addrs > 0 && bleft <= 0 && read(fd, &c, 1) == 1)
414 		addrs--;
415 	close(fd);
416 
417 	*besterr = 0;
418 	rv = -1;				/* pessimistic default */
419 	dp->naddrs = addrs;
420 	if (addrs == 0)
421 		werrstr("no address to dial");
422 	else if (addrs == 1) {
423 		/* common case: dial one address without forking */
424 		if (parsecs(dp, &clone2, &dest) >= 0 &&
425 		    (rv = call(clone2, dest, ds, dp, &dp->conn[0])) < 0) {
426 			pickuperr(besterr, err);
427 			werrstr("%s", besterr);
428 		}
429 	} else if (connsalloc(dp, addrs) >= 0)
430 		rv = dialmulti(ds, dp);
431 
432 	/* fill in results */
433 	if (rv >= 0 && dp->winner >= 0)
434 		rv = fillinds(ds, dp);
435 
436 	freedest(dp);
437 	return rv;
438 }
439 
440 static int
call(char * clone,char * dest,DS * ds,Dest * dp,Conn * conn)441 call(char *clone, char *dest, DS *ds, Dest *dp, Conn *conn)
442 {
443 	int fd, cfd, n, calleralarm, oalarm;
444 	char cname[Maxpath], name[Maxpath], data[Maxpath], *p;
445 
446 	/* because cs is in a different name space, replace the mount point */
447 	if(*clone == '/'){
448 		p = strchr(clone+1, '/');
449 		if(p == nil)
450 			p = clone;
451 		else
452 			p++;
453 	} else
454 		p = clone;
455 	snprint(cname, sizeof cname, "%s/%s", ds->netdir, p);
456 
457 	conn->pid = getpid();
458 	conn->cfd = cfd = open(cname, ORDWR);
459 	if(cfd < 0)
460 		return -1;
461 
462 	/* get directory name */
463 	n = read(cfd, name, sizeof(name)-1);
464 	if(n < 0){
465 		closeopenfd(&conn->cfd);
466 		return -1;
467 	}
468 	name[n] = 0;
469 	for(p = name; *p == ' '; p++)
470 		;
471 	snprint(name, sizeof(name), "%ld", strtoul(p, 0, 0));
472 	p = strrchr(cname, '/');
473 	*p = 0;
474 	if(ds->dir)
475 		snprint(conn->dir, NETPATHLEN, "%s/%s", cname, name);
476 	snprint(data, sizeof(data), "%s/%s/data", cname, name);
477 
478 	/* should be no alarm pending now; re-instate caller's alarm, if any */
479 	calleralarm = dp->oalarm > 0;
480 	if (calleralarm)
481 		alarm(dp->oalarm);
482 	else if (dp->naddrs > 1)	/* in a sub-process? */
483 		alarm(Maxconnms);
484 
485 	/* connect */
486 	if(ds->local)
487 		snprint(name, sizeof(name), "connect %s %s", dest, ds->local);
488 	else
489 		snprint(name, sizeof(name), "connect %s", dest);
490 	if(write(cfd, name, strlen(name)) < 0){
491 		closeopenfd(&conn->cfd);
492 		return -1;
493 	}
494 
495 	oalarm = alarm(0);	/* don't let alarm interrupt critical section */
496 	if (calleralarm)
497 		dp->oalarm = oalarm;	/* time has passed, so update user's */
498 
499 	/* open data connection */
500 	conn->dfd = fd = open(data, ORDWR);
501 	if(fd < 0){
502 		closeopenfd(&conn->cfd);
503 		alarm(dp->oalarm);
504 		return -1;
505 	}
506 	if(ds->cfdp == nil)
507 		closeopenfd(&conn->cfd);
508 
509 	n = conn - dp->conn;
510 	if (dp->winner < 0) {
511 		qlock(&dp->winlck);
512 		if (dp->winner < 0 && conn < dp->connend)
513 			dp->winner = n;
514 		qunlock(&dp->winlck);
515 	}
516 	alarm(calleralarm? dp->oalarm: 0);
517 	return fd;
518 }
519 
520 /*
521  *  parse a dial string
522  */
523 static void
_dial_string_parse(char * str,DS * ds)524 _dial_string_parse(char *str, DS *ds)
525 {
526 	char *p, *p2;
527 
528 	strncpy(ds->buf, str, Maxstring);
529 	ds->buf[Maxstring-1] = 0;
530 
531 	p = strchr(ds->buf, '!');
532 	if(p == 0) {
533 		ds->netdir = 0;
534 		ds->proto = "net";
535 		ds->rem = ds->buf;
536 	} else {
537 		if(*ds->buf != '/' && *ds->buf != '#'){
538 			ds->netdir = 0;
539 			ds->proto = ds->buf;
540 		} else {
541 			/* expecting /net.alt/tcp!foo or #I1/tcp!foo */
542 			for(p2 = p; p2 > ds->buf && *p2 != '/'; p2--)
543 				;
544 			*p2++ = 0;
545 			ds->netdir = ds->buf;
546 			ds->proto = p2;
547 		}
548 		*p = 0;
549 		ds->rem = p + 1;
550 	}
551 }
552