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