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 (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+1];
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 - 1);
189
			conn->err[sizeof conn->err - 1] = '\0';
190
			conn->dead = 1;
191
			return;
192
		}
193
	/* not a proc that we forked */
194
}
195
 
196
static int
197
outstandingprocs(Dest *dp)
198
{
199
	Conn *conn;
200
 
201
	for (conn = dp->conn; conn < dp->connend; conn++)
202
		if (!conn->dead)
203
			return 1;
204
	return 0;
205
}
206
 
207
static int
208
reap(Dest *dp)
209
{
210
	char exitsts[2*ERRMAX];
211
 
212
	if (outstandingprocs(dp) && await(exitsts, sizeof exitsts) >= 0) {
213
		notedeath(dp, exitsts);
214
		return 0;
215
	}
216
	return -1;
217
}
218
 
219
static int
220
fillinds(DS *ds, Dest *dp)
221
{
222
	Conn *conn;
223
 
224
	if (dp->winner < 0)
225
		return -1;
226
	conn = &dp->conn[dp->winner];
227
	if (ds->cfdp)
228
		*ds->cfdp = conn->cfd;
229
	if (ds->dir) {
230
		strncpy(ds->dir, conn->dir, NETPATHLEN);
231
		ds->dir[NETPATHLEN-1] = '\0';
232
	}
233
	return conn->dfd;
234
}
235
 
236
static int
237
connectwait(Dest *dp, char *besterr)
238
{
239
	Conn *conn;
240
 
241
	/* wait for a winner or all attempts to time out */
242
	while (dp->winner < 0 && reap(dp) >= 0)
243
		;
244
 
245
	/* kill all of our still-live kids & reap them */
246
	for (conn = dp->conn; conn < dp->connend; conn++)
247
		if (!conn->dead)
248
			postnote(PNPROC, conn->pid, "alarm");
249
	while (reap(dp) >= 0)
250
		;
251
 
252
	/* rummage about and report some error string */
253
	for (conn = dp->conn; conn < dp->connend; conn++)
254
		if (conn - dp->conn != dp->winner && conn->dead &&
255
		    conn->err[0]) {
256
			strncpy(besterr, conn->err, ERRMAX-1);
257
			conn->err[ERRMAX-1] = '\0';
258
			break;
259
		}
260
	return dp->winner;
261
}
262
 
263
static int
264
parsecs(Dest *dp, char **clonep, char **destp)
265
{
266
	char *dest, *p;
267
 
268
	dest = strchr(dp->nextaddr, ' ');
269
	if(dest == nil)
270
		return -1;
271
	*dest++ = '\0';
272
	p = strchr(dest, '\n');
273
	if(p == nil)
274
		return -1;
275
	*p++ = '\0';
276
	*clonep = dp->nextaddr;
277
	*destp = dest;
278
	dp->nextaddr = p;		/* advance to next line */
279
	return 0;
280
}
281
 
282
static void
283
pickuperr(char *besterr, char *err)
284
{
285
	err[0] = '\0';
286
	errstr(err, ERRMAX);
287
	if(strstr(err, "does not exist") == 0)
288
		strcpy(besterr, err);
289
}
290
 
291
static int
292
catcher(void *, char *s)
293
{
294
	return strstr(s, "alarm") != nil;
295
}
296
 
297
/*
298
 * try all addresses in parallel and take the first one that answers;
299
 * this helps when systems have ip v4 and v6 addresses but are
300
 * only reachable from here on one (or some) of them.
301
 */
302
static int
303
dialmulti(DS *ds, Dest *dp)
304
{
305
	int rv, kid, kidme;
306
	char *clone, *dest;
307
	char err[ERRMAX], besterr[ERRMAX];
308
 
309
	dp->winner = -1;
310
	dp->nkid = 0;
311
	while(dp->winner < 0 && *dp->nextaddr != '\0' &&
312
	    parsecs(dp, &clone, &dest) >= 0) {
313
		kidme = dp->nkid++;		/* make private copy on stack */
314
		kid = rfork(RFPROC|RFMEM);	/* spin off a call attempt */
315
		if (kid < 0)
316
			--dp->nkid;
317
		else if (kid == 0) {
318
			/* only in kid, to avoid atnotify callbacks in parent */
319
			atnotify(catcher, 1);
320
 
321
			*besterr = '\0';
322
			rv = call(clone, dest, ds, dp, &dp->conn[kidme]);
323
			if(rv < 0)
324
				pickuperr(besterr, err);
325
			_exits(besterr);	/* avoid atexit callbacks */
326
		}
327
	}
328
	rv = connectwait(dp, besterr);
329
	if(rv < 0 && *besterr)
330
		werrstr("%s", besterr);
331
	else
332
		werrstr("%s", err);
333
	return rv;
334
}
335
 
336
static int
337
csdial(DS *ds)
338
{
339
	int n, fd, rv, addrs, bleft;
340
	char c;
341
	char *addrp, *clone2, *dest;
342
	char buf[Maxstring], clone[Maxpath], err[ERRMAX], besterr[ERRMAX];
343
	Dest *dp;
344
 
345
	dp = mallocz(sizeof *dp, 1);
346
	if(dp == nil)
347
		return -1;
348
	dp->winner = -1;
349
	dp->oalarm = alarm(0);
350
	if (connsalloc(dp, 1) < 0) {		/* room for a single conn. */
351
		freedest(dp);
352
		return -1;
353
	}
354
 
355
	/*
356
	 *  open connection server
357
	 */
358
	snprint(buf, sizeof(buf), "%s/cs", ds->netdir);
359
	fd = open(buf, ORDWR);
360
	if(fd < 0){
361
		/* no connection server, don't translate */
362
		snprint(clone, sizeof(clone), "%s/%s/clone", ds->netdir, ds->proto);
363
		rv = call(clone, ds->rem, ds, dp, &dp->conn[0]);
364
		fillinds(ds, dp);
365
		freedest(dp);
366
		return rv;
367
	}
368
 
369
	/*
370
	 *  ask connection server to translate
371
	 */
372
	snprint(buf, sizeof(buf), "%s!%s", ds->proto, ds->rem);
373
	if(write(fd, buf, strlen(buf)) < 0){
374
		close(fd);
375
		freedest(dp);
376
		return -1;
377
	}
378
 
379
	/*
380
	 *  read all addresses from the connection server.
381
	 */
382
	seek(fd, 0, 0);
383
	addrs = 0;
384
	addrp = dp->nextaddr = dp->addrlist;
385
	bleft = sizeof dp->addrlist - 2;	/* 2 is room for \n\0 */
386
	while(bleft > 0 && (n = read(fd, addrp, bleft)) > 0) {
387
		if (addrp[n-1] != '\n')
388
			addrp[n++] = '\n';
389
		addrs++;
390
		addrp += n;
391
		bleft -= n;
392
	}
393
	/*
394
	 * if we haven't read all of cs's output, assume the last line might
395
	 * have been truncated and ignore it.  we really don't expect this
396
	 * to happen.
397
	 */
398
	if (addrs > 0 && bleft <= 0 && read(fd, &c, 1) == 1)
399
		addrs--;
400
	close(fd);
401
 
402
	*besterr = 0;
403
	rv = -1;				/* pessimistic default */
404
	dp->naddrs = addrs;
405
	if (addrs == 0)
406
		werrstr("no address to dial");
407
	else if (addrs == 1) {
408
		/* common case: dial one address without forking */
409
		if (parsecs(dp, &clone2, &dest) >= 0 &&
410
		    (rv = call(clone2, dest, ds, dp, &dp->conn[0])) < 0) {
411
			pickuperr(besterr, err);
412
			werrstr("%s", besterr);
413
		}
414
	} else if (connsalloc(dp, addrs) >= 0)
415
		rv = dialmulti(ds, dp);
416
 
417
	/* fill in results */
418
	if (rv >= 0 && dp->winner >= 0)
419
		rv = fillinds(ds, dp);
420
 
421
	freedest(dp);
422
	return rv;
423
}
424
 
425
static int
426
call(char *clone, char *dest, DS *ds, Dest *dp, Conn *conn)
427
{
428
	int fd, cfd, n, calleralarm, oalarm;
429
	char cname[Maxpath], name[Maxpath], data[Maxpath], *p;
430
 
431
	/* because cs is in a different name space, replace the mount point */
432
	if(*clone == '/'){
433
		p = strchr(clone+1, '/');
434
		if(p == nil)
435
			p = clone;
436
		else 
437
			p++;
438
	} else
439
		p = clone;
440
	snprint(cname, sizeof cname, "%s/%s", ds->netdir, p);
441
 
442
	conn->pid = getpid();
443
	conn->cfd = cfd = open(cname, ORDWR);
444
	if(cfd < 0)
445
		return -1;
446
 
447
	/* get directory name */
448
	n = read(cfd, name, sizeof(name)-1);
449
	if(n < 0){
450
		closeopenfd(&conn->cfd);
451
		return -1;
452
	}
453
	name[n] = 0;
454
	for(p = name; *p == ' '; p++)
455
		;
456
	snprint(name, sizeof(name), "%ld", strtoul(p, 0, 0));
457
	p = strrchr(cname, '/');
458
	*p = 0;
459
	if(ds->dir)
460
		snprint(conn->dir, NETPATHLEN, "%s/%s", cname, name);
461
	snprint(data, sizeof(data), "%s/%s/data", cname, name);
462
 
463
	/* should be no alarm pending now; re-instate caller's alarm, if any */
464
	calleralarm = dp->oalarm > 0;
465
	if (calleralarm)
466
		alarm(dp->oalarm);
467
	else if (dp->naddrs > 1)	/* in a sub-process? */
468
		alarm(Maxconnms);
469
 
470
	/* connect */
471
	if(ds->local)
472
		snprint(name, sizeof(name), "connect %s %s", dest, ds->local);
473
	else
474
		snprint(name, sizeof(name), "connect %s", dest);
475
	if(write(cfd, name, strlen(name)) < 0){
476
		closeopenfd(&conn->cfd);
477
		return -1;
478
	}
479
 
480
	oalarm = alarm(0);	/* don't let alarm interrupt critical section */
481
	if (calleralarm)
482
		dp->oalarm = oalarm;	/* time has passed, so update user's */
483
 
484
	/* open data connection */
485
	conn->dfd = fd = open(data, ORDWR);
486
	if(fd < 0){
487
		closeopenfd(&conn->cfd);
488
		alarm(dp->oalarm);
489
		return -1;
490
	}
491
	if(ds->cfdp == nil)
492
		closeopenfd(&conn->cfd);
493
 
494
	n = conn - dp->conn;
495
	if (dp->winner < 0) {
496
		qlock(&dp->winlck);
497
		if (dp->winner < 0 && conn < dp->connend)
498
			dp->winner = n;
499
		qunlock(&dp->winlck);
500
	}
501
	alarm(calleralarm? dp->oalarm: 0);
502
	return fd;
503
}
504
 
505
/*
506
 * assume p points at first '!' in dial string.  st is start of dial string.
507
 * there could be subdirs of the conn dirs (e.g., ssh/0) that must count as
508
 * part of the proto string, so skip numeric components.
509
 * returns pointer to delimiter after right-most non-numeric component.
510
 */
511
static char *
512
backoverchans(char *st, char *p)
513
{
514
	char *sl;
515
 
516
	for (sl = p; --p >= st && isascii(*p) && isdigit(*p); sl = p) {
517
		while (--p >= st && isascii(*p) && isdigit(*p))
518
			;
519
		if (p < st || *p != '/')
520
			break;			/* "net.alt2" or ran off start */
521
		while (p > st && p[-1] == '/')	/* skip runs of slashes */
522
			p--;
523
	}
524
	return sl;
525
}
526
 
527
/*
528
 *  parse a dial string
529
 */
530
static void
531
_dial_string_parse(char *str, DS *ds)
532
{
533
	char *p, *p2;
534
 
535
	strncpy(ds->buf, str, Maxstring);
536
	ds->buf[Maxstring-1] = 0;
537
 
538
	p = strchr(ds->buf, '!');
539
	if(p == 0) {
540
		ds->netdir = 0;
541
		ds->proto = "net";
542
		ds->rem = ds->buf;
543
	} else {
544
		if(*ds->buf != '/' && *ds->buf != '#'){
545
			ds->netdir = 0;
546
			ds->proto = ds->buf;
547
		} else {
548
			p2 = backoverchans(ds->buf, p);
549
 
550
			/* back over last component of netdir (proto) */
551
			while (--p2 > ds->buf && *p2 != '/')
552
				;
553
			*p2++ = 0;
554
			ds->netdir = ds->buf;
555
			ds->proto = p2;
556
		}
557
		*p = 0;
558
		ds->rem = p + 1;
559
	}
560
}