Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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
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
120
dial(char *dest, char *local, char *dir, int *cfdp)
121
{
122
	return (*_dial)(dest, local, dir, cfdp);
123
}
124
 
125
static int
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
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
158
closeopenfd(int *fdp)
159
{
160
	if (*fdp >= 0) {
161
		close(*fdp);
162
		*fdp = -1;
163
	}
164
}
165
 
166
static void
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
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
		ds->dir[NETPATHLEN-1] = '\0';
231
	}
232
	return conn->dfd;
233
}
234
 
235
static int
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
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
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
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
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
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
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
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
}