Subversion Repositories planix.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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-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
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-1] = '\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
}