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