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