]> git.lizzy.rs Git - plan9front.git/blob - sys/src/libc/9sys/dial.c
Import sources from 2011-03-30 iso image
[plan9front.git] / sys / src / libc / 9sys / dial.c
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       = 20*60*1000,   /* 20 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];
49         char    err[ERRMAX];
50 };
51 struct Dest {
52         Conn    *conn;                  /* allocated array */
53         Conn    *connend;
54         int     nkid;
55
56         QLock   winlck;
57         int     winner;                 /* index into conn[] */
58
59         char    *nextaddr;
60         char    addrlist[Maxcsreply];
61 };
62
63 static int      call(char*, char*, DS*, Dest*, Conn*);
64 static int      csdial(DS*);
65 static void     _dial_string_parse(char*, DS*);
66
67
68 /*
69  *  the dialstring is of the form '[/net/]proto!dest'
70  */
71 static int
72 dialimpl(char *dest, char *local, char *dir, int *cfdp)
73 {
74         DS ds;
75         int rv;
76         char err[ERRMAX], alterr[ERRMAX];
77
78         ds.local = local;
79         ds.dir = dir;
80         ds.cfdp = cfdp;
81
82         _dial_string_parse(dest, &ds);
83         if(ds.netdir)
84                 return csdial(&ds);
85
86         ds.netdir = "/net";
87         rv = csdial(&ds);
88         if(rv >= 0)
89                 return rv;
90         err[0] = '\0';
91         errstr(err, sizeof err);
92         if(strstr(err, "refused") != 0){
93                 werrstr("%s", err);
94                 return rv;
95         }
96         ds.netdir = "/net.alt";
97         rv = csdial(&ds);
98         if(rv >= 0)
99                 return rv;
100
101         alterr[0] = 0;
102         errstr(alterr, sizeof alterr);
103         if(strstr(alterr, "translate") || strstr(alterr, "does not exist"))
104                 werrstr("%s", err);
105         else
106                 werrstr("%s", alterr);
107         return rv;
108 }
109
110 /*
111  * the thread library can't cope with rfork(RFMEM|RFPROC),
112  * so it must override this with a private version of dial.
113  */
114 int (*_dial)(char *, char *, char *, int *) = dialimpl;
115
116 int
117 dial(char *dest, char *local, char *dir, int *cfdp)
118 {
119         return (*_dial)(dest, local, dir, cfdp);
120 }
121
122 static int
123 connsalloc(Dest *dp, int addrs)
124 {
125         free(dp->conn);
126         dp->connend = nil;
127         assert(addrs > 0);
128
129         dp->conn = mallocz(addrs * sizeof *dp->conn, 1);
130         if(dp->conn == nil)
131                 return -1;
132         dp->connend = dp->conn + addrs;
133         return 0;
134 }
135
136 static void
137 freedest(Dest *dp)
138 {
139         if (dp != nil) {
140                 free(dp->conn);
141                 free(dp);
142         }
143 }
144
145 static void
146 closeopenfd(int *fdp)
147 {
148         if (*fdp > 0) {
149                 close(*fdp);
150                 *fdp = -1;
151         }
152 }
153
154 static void
155 notedeath(Dest *dp, char *exitsts)
156 {
157         int i, n, pid;
158         char *fields[5];                        /* pid + 3 times + error */
159         Conn *conn;
160
161         for (i = 0; i < nelem(fields); i++)
162                 fields[i] = "";
163         n = tokenize(exitsts, fields, nelem(fields));
164         if (n < 4)
165                 return;
166         pid = atoi(fields[0]);
167         if (pid <= 0)
168                 return;
169         for (conn = dp->conn; conn < dp->connend; conn++)
170                 if (conn->pid == pid && !conn->dead) {  /* it's one we know? */
171                         if (conn - dp->conn != dp->winner) {
172                                 closeopenfd(&conn->dfd);
173                                 closeopenfd(&conn->cfd);
174                         }
175                         strncpy(conn->err, fields[4], sizeof conn->err);
176                         conn->dead = 1;
177                         return;
178                 }
179         /* not a proc that we forked */
180 }
181
182 static int
183 outstandingprocs(Dest *dp)
184 {
185         Conn *conn;
186
187         for (conn = dp->conn; conn < dp->connend; conn++)
188                 if (!conn->dead)
189                         return 1;
190         return 0;
191 }
192
193 static int
194 reap(Dest *dp)
195 {
196         char exitsts[2*ERRMAX];
197
198         if (outstandingprocs(dp) && await(exitsts, sizeof exitsts) >= 0) {
199                 notedeath(dp, exitsts);
200                 return 0;
201         }
202         return -1;
203 }
204
205 static int
206 fillinds(DS *ds, Dest *dp)
207 {
208         Conn *conn;
209
210         if (dp->winner < 0)
211                 return -1;
212         conn = &dp->conn[dp->winner];
213         if (ds->cfdp)
214                 *ds->cfdp = conn->cfd;
215         if (ds->dir)
216                 strncpy(ds->dir, conn->dir, NETPATHLEN);
217         return conn->dfd;
218 }
219
220 static int
221 connectwait(Dest *dp, char *besterr)
222 {
223         Conn *conn;
224
225         /* wait for a winner or all attempts to time out */
226         while (dp->winner < 0 && reap(dp) >= 0)
227                 ;
228
229         /* kill all of our still-live kids & reap them */
230         for (conn = dp->conn; conn < dp->connend; conn++)
231                 if (!conn->dead)
232                         postnote(PNPROC, conn->pid, "die");
233         while (reap(dp) >= 0)
234                 ;
235
236         /* rummage about and report some error string */
237         for (conn = dp->conn; conn < dp->connend; conn++)
238                 if (conn - dp->conn != dp->winner && conn->dead &&
239                     conn->err[0]) {
240                         strncpy(besterr, conn->err, ERRMAX);
241                         break;
242                 }
243         return dp->winner;
244 }
245
246 static int
247 parsecs(Dest *dp, char **clonep, char **destp)
248 {
249         char *dest, *p;
250
251         dest = strchr(dp->nextaddr, ' ');
252         if(dest == nil)
253                 return -1;
254         *dest++ = '\0';
255         p = strchr(dest, '\n');
256         if(p == nil)
257                 return -1;
258         *p++ = '\0';
259         *clonep = dp->nextaddr;
260         *destp = dest;
261         dp->nextaddr = p;               /* advance to next line */
262         return 0;
263 }
264
265 static void
266 pickuperr(char *besterr, char *err)
267 {
268         err[0] = '\0';
269         errstr(err, ERRMAX);
270         if(strstr(err, "does not exist") == 0)
271                 strcpy(besterr, err);
272 }
273
274 /*
275  * try all addresses in parallel and take the first one that answers;
276  * this helps when systems have ip v4 and v6 addresses but are
277  * only reachable from here on one (or some) of them.
278  */
279 static int
280 dialmulti(DS *ds, Dest *dp)
281 {
282         int rv, kid, kidme;
283         char *clone, *dest;
284         char err[ERRMAX], besterr[ERRMAX];
285
286         dp->winner = -1;
287         dp->nkid = 0;
288         while(dp->winner < 0 && *dp->nextaddr != '\0' &&
289             parsecs(dp, &clone, &dest) >= 0) {
290                 kidme = dp->nkid++;             /* make private copy on stack */
291                 kid = rfork(RFPROC|RFMEM);      /* spin off a call attempt */
292                 if (kid < 0)
293                         --dp->nkid;
294                 else if (kid == 0) {
295                         alarm(Maxconnms);
296                         *besterr = '\0';
297                         rv = call(clone, dest, ds, dp, &dp->conn[kidme]);
298                         if(rv < 0)
299                                 pickuperr(besterr, err);
300                         _exits(besterr);        /* avoid atexit callbacks */
301                 }
302         }
303         rv = connectwait(dp, besterr);
304         if(rv < 0 && *besterr)
305                 werrstr("%s", besterr);
306         else
307                 werrstr("%s", err);
308         return rv;
309 }
310
311 static int
312 csdial(DS *ds)
313 {
314         int n, fd, rv, addrs, bleft;
315         char c;
316         char *addrp, *clone2, *dest;
317         char buf[Maxstring], clone[Maxpath], err[ERRMAX], besterr[ERRMAX];
318         Dest *dp;
319
320         dp = mallocz(sizeof *dp, 1);
321         if(dp == nil)
322                 return -1;
323         dp->winner = -1;
324         if (connsalloc(dp, 1) < 0) {            /* room for a single conn. */
325                 freedest(dp);
326                 return -1;
327         }
328
329         /*
330          *  open connection server
331          */
332         snprint(buf, sizeof(buf), "%s/cs", ds->netdir);
333         fd = open(buf, ORDWR);
334         if(fd < 0){
335                 /* no connection server, don't translate */
336                 snprint(clone, sizeof(clone), "%s/%s/clone", ds->netdir, ds->proto);
337                 rv = call(clone, ds->rem, ds, dp, &dp->conn[0]);
338                 fillinds(ds, dp);
339                 freedest(dp);
340                 return rv;
341         }
342
343         /*
344          *  ask connection server to translate
345          */
346         snprint(buf, sizeof(buf), "%s!%s", ds->proto, ds->rem);
347         if(write(fd, buf, strlen(buf)) < 0){
348                 close(fd);
349                 freedest(dp);
350                 return -1;
351         }
352
353         /*
354          *  read all addresses from the connection server.
355          */
356         seek(fd, 0, 0);
357         addrs = 0;
358         addrp = dp->nextaddr = dp->addrlist;
359         bleft = sizeof dp->addrlist - 2;        /* 2 is room for \n\0 */
360         while(bleft > 0 && (n = read(fd, addrp, bleft)) > 0) {
361                 if (addrp[n-1] != '\n')
362                         addrp[n++] = '\n';
363                 addrs++;
364                 addrp += n;
365                 bleft -= n;
366         }
367         /*
368          * if we haven't read all of cs's output, assume the last line might
369          * have been truncated and ignore it.  we really don't expect this
370          * to happen.
371          */
372         if (addrs > 0 && bleft <= 0 && read(fd, &c, 1) == 1)
373                 addrs--;
374         close(fd);
375
376         *besterr = 0;
377         rv = -1;                                /* pessimistic default */
378         if (addrs == 0)
379                 werrstr("no address to dial");
380         else if (addrs == 1) {
381                 /* common case: dial one address without forking */
382                 if (parsecs(dp, &clone2, &dest) >= 0 &&
383                     (rv = call(clone2, dest, ds, dp, &dp->conn[0])) < 0) {
384                         pickuperr(besterr, err);
385                         werrstr("%s", besterr);
386                 }
387         } else if (connsalloc(dp, addrs) >= 0)
388                 rv = dialmulti(ds, dp);
389
390         /* fill in results */
391         if (rv >= 0 && dp->winner >= 0)
392                 rv = fillinds(ds, dp);
393
394         freedest(dp);
395         return rv;
396 }
397
398 static int
399 call(char *clone, char *dest, DS *ds, Dest *dp, Conn *conn)
400 {
401         int fd, cfd, n;
402         char cname[Maxpath], name[Maxpath], data[Maxpath], *p;
403
404         /* because cs is in a different name space, replace the mount point */
405         if(*clone == '/'){
406                 p = strchr(clone+1, '/');
407                 if(p == nil)
408                         p = clone;
409                 else 
410                         p++;
411         } else
412                 p = clone;
413         snprint(cname, sizeof cname, "%s/%s", ds->netdir, p);
414
415         conn->pid = getpid();
416         conn->cfd = cfd = open(cname, ORDWR);
417         if(cfd < 0)
418                 return -1;
419
420         /* get directory name */
421         n = read(cfd, name, sizeof(name)-1);
422         if(n < 0){
423                 closeopenfd(&conn->cfd);
424                 return -1;
425         }
426         name[n] = 0;
427         for(p = name; *p == ' '; p++)
428                 ;
429         snprint(name, sizeof(name), "%ld", strtoul(p, 0, 0));
430         p = strrchr(cname, '/');
431         *p = 0;
432         if(ds->dir)
433                 snprint(conn->dir, NETPATHLEN, "%s/%s", cname, name);
434         snprint(data, sizeof(data), "%s/%s/data", cname, name);
435
436         /* connect */
437         if(ds->local)
438                 snprint(name, sizeof(name), "connect %s %s", dest, ds->local);
439         else
440                 snprint(name, sizeof(name), "connect %s", dest);
441         if(write(cfd, name, strlen(name)) < 0){
442                 closeopenfd(&conn->cfd);
443                 return -1;
444         }
445
446         /* open data connection */
447         conn->dfd = fd = open(data, ORDWR);
448         if(fd < 0){
449                 closeopenfd(&conn->cfd);
450                 return -1;
451         }
452         if(ds->cfdp == nil)
453                 closeopenfd(&conn->cfd);
454
455         qlock(&dp->winlck);
456         if (dp->winner < 0 && conn < dp->connend)
457                 dp->winner = conn - dp->conn;
458         qunlock(&dp->winlck);
459         return fd;
460 }
461
462 /*
463  *  parse a dial string
464  */
465 static void
466 _dial_string_parse(char *str, DS *ds)
467 {
468         char *p, *p2;
469
470         strncpy(ds->buf, str, Maxstring);
471         ds->buf[Maxstring-1] = 0;
472
473         p = strchr(ds->buf, '!');
474         if(p == 0) {
475                 ds->netdir = 0;
476                 ds->proto = "net";
477                 ds->rem = ds->buf;
478         } else {
479                 if(*ds->buf != '/' && *ds->buf != '#'){
480                         ds->netdir = 0;
481                         ds->proto = ds->buf;
482                 } else {
483                         for(p2 = p; *p2 != '/'; p2--)
484                                 ;
485                         *p2++ = 0;
486                         ds->netdir = ds->buf;
487                         ds->proto = p2;
488                 }
489                 *p = 0;
490                 ds->rem = p + 1;
491         }
492 }