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 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 137 dial(char *dest, char *local, char *dir, int *cfdp) 138 { 139 return (*_dial)(dest, local, dir, cfdp); 140 } 141 142 static void 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 153 closeopenfd(int *fdp) 154 { 155 if (*fdp >= 0) { 156 close(*fdp); 157 *fdp = -1; 158 } 159 } 160 161 static int 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 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 192 catcher(void *, char *s) 193 { 194 return strstr(s, "alarm") != nil; 195 } 196 197 static void 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 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 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] = '\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 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 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] = '\0'; 337 } 338 return dfd; 339 } 340 341 static int 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 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 * 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 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