1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1990-2011 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                 Eclipse Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *          http://www.eclipse.org/org/documents/epl-v10.html           *
11 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                 Glenn Fowler <gsf@research.att.com>                  *
18 *                                                                      *
19 ***********************************************************************/
20 #pragma prototyped
21 /*
22  * Glenn Fowler
23  * AT&T Research
24  *
25  * cs - connect stream control
26  */
27 
28 static const char usage[] =
29 "[-?\n@(#)$Id: cs (AT&T Research) 2006-06-11 $\n]"
30 USAGE_LICENSE
31 "[+NAME?cs - connect stream control]"
32 "[+DESCRIPTION?\bcs\b displays, initiates, and terminates connect stream"
33 "	services, and displays the contents of connect stream message files."
34 "	If no options are spcified then the connect stream for \apath\a is"
35 "	opened. If the corresponding service is not running then it is"
36 "	initiated and the connection is attempted again. If \acommand\a is"
37 "	specified then it is executed with the standard input, standard"
38 "	output and standard error redirected to the \apath\a connect stream."
39 "	If \acommand\a is omitted then the \b/dev/\b equivalent path for"
40 "	the connect stream is listed on the standard output.]"
41 
42 "[a:attribute?List the attribute \aname\a for each host. If \aname\a is \b-\b"
43 "	then all attributes are listed. The hostname attribute is listed"
44 "	without a label, all other attributes are listed as"
45 "	\alabel\a=\avalue\a]:[name]"
46 "[c:cat?Catenate messages in the named \apath\a operands. If \apath\a is"
47 "	omitted then the standard input is read.]"
48 "[d:debug?Set the debug trace level to \alevel\a. Higher levels produce"
49 "	more output.]#[level]"
50 "[f:continuous?Used with \b--cat\b to list messages on a \apath\a or standard"
51 "	input that is continuously updated.]"
52 "[h:hostenvironment?Lists \bHOSTNAME\b=\aname\a \bHOSTTYPE\b=\atype\a on the"
53 "	standard output for the named \ahost\a operand or the local host if"
54 "	\ahost\a is omitted. This is useful for \b.profile\b initialization.]"
55 "[i:interactive?Open an interactive connection to the connect stream. The"
56 "	service is initiated if it is not already running.]"
57 "[k:kill?Send \asignal\a to the server on the connect stream named by"
58 "	\apath\a. \asignal\a may be a signal name or signal number.]:[signal]"
59 "[l:list?List the active connect stream services on the standard output.]"
60 "[m:mount?List the active connect stream mount directories on the standard"
61 "	output.]"
62 "[p:process?List the active connect stream process ids on the standard output.]"
63 "[q:query?Open an interactive connection to the connect stream if a service"
64 "	is already running; fail otherwise.]"
65 "[r:raw?Raw mode \b--interactive\b connection.]"
66 "[s:iservice?List details for each active connect stream service on the"
67 "	standard output. The output format is similar to an \bls\b(1)"
68 "	\b--long\b listing, except the size field is the \btcp\b or \budp\b"
69 "	port number, and the service base name appears as a symbolic link"
70 "	to the network \b/proc\b path for the service process.]"
71 "[t:translate?\ahost\a name operands are translated to IP address dot notation"
72 "	and listed on the standard output. If \ahost\a is omitted then the"
73 "	standard input is read for host names, one per line.]"
74 "[C:call?Used with \b--cat\b to list only messages for the calls in"
75 "	\acall-list\a.]:[call-list]"
76 "[O:open-flags?Set optional \bcsopen\b(3) flags. Used by the \bcs\b(3) library"
77 "	to initiate remote connections.]:[flags]"
78 "[T:terse?Used with \b--cat\b to list terse messages for the calls in"
79 "	\acall-list\a]:[call-list]"
80 
81 "\n"
82 "\n[ [ - ] host | path [ command ... ] ]\n"
83 "\n"
84 
85 "[+DATA?Static information for hosts in the local network is in the file"
86 "	\b../share/lib/cs/local\b on \b$PATH\b. Each line in the \blocal\b"
87 "	file provides information for a single host. The syntax is:"
88 "	\ahost-name\a [ \aattribute\a=\avalue\a ... ]]. Attributes for the host"
89 "	\blocal\b are inherited by all hosts. Locally administered attributes"
90 "	may be added. \aattribute\a with predefined semantics are:]{"
91 "		[+addr?The host IP address in dot notation.]"
92 "		[+bias?The \bcoshell\b(1) multiplies the host load by \bbias\b"
93 "			to prioritize host availability. \bbias\b > 1 makes"
94 "			the host less likely to be chosen.]"
95 "		[+busy?\bcoshell\b(1) jobs running on a host that has remained"
96 "			busy for this amount of time are suspended until the"
97 "			host returns to idle status.]"
98 "		[+cpu?The number of cpus on the host as reported by"
99 "			\bpackage\b(1).]"
100 "		[+idle?The minimum interactive user idle time before"
101 "			\bcoshell\b(1) will schedule a job on the host.]"
102 "		[+pool?The \bcoshell\b(1) attempts to keep \bpool\b"
103 "			host connections active.]"
104 "		[+rating?The host rating as reported by \bpackage\b(1).]"
105 "		[+type?The host type as reported by \bpackage\b(1).]"
106 "}"
107 "[+FILES]{"
108 "	[+../share/lib/cs/local?Local host info list on \b$PATH\b.]"
109 "	[+../share/lib/ss/\ahost\a?Host status files on \b$PATH\b.]"
110 "}"
111 
112 "[+SEE ALSO?\bcoshell\b(1), \bcss\b(1), \bpackage\b(1), \bss\b(1), \bcs\b(3)]"
113 ;
114 
115 #include <ast.h>
116 #include <coshell.h>
117 #include <cs.h>
118 #include <error.h>
119 #include <ftwalk.h>
120 #include <msg.h>
121 #include <proc.h>
122 #include <sig.h>
123 #include <tm.h>
124 #include <tok.h>
125 #include <debug.h>
126 
127 #define LIST		(1<<0)
128 #define LIST_MOUNT	(1<<1)
129 #define LIST_PROCESS	(1<<2)
130 #define LIST_SERVICE	(1<<3)
131 
132 #define MSG_LIST		(MSG_LIST_USER<<0)
133 #define MSG_LIST_CONTINUOUS	(MSG_LIST_USER<<1)
134 
135 static struct
136 {
137 	int		list;		/* list flags			*/
138 	char*		local;		/* csname(0)			*/
139 } state;
140 
141 /*
142  * host address translation
143  */
144 
145 static void
address(const char * name)146 address(const char* name)
147 {
148 	unsigned long	addr;
149 
150 	if (addr = csaddr(name))
151 		sfprintf(sfstdout, "name=%s addr=%s host=%s user=%s flags=:%s%s%s%s%s\n"
152 			, csfull(addr)
153 			, csntoa(addr)
154 			, cs.host
155 			, cs.user
156 			, (cs.flags & CS_ADDR_LOCAL) ? "LOCAL:" : ""
157 			, (cs.flags & CS_ADDR_NUMERIC) ? "NUMERIC:" : ""
158 			, (cs.flags & CS_ADDR_SHARE) ? "SHARE:" : ""
159 			, (cs.flags & CS_ADDR_REMOTE) ? "REMOTE:" : ""
160 			, (cs.flags & (CS_ADDR_LOCAL|CS_ADDR_NUMERIC|CS_ADDR_SHARE|CS_ADDR_REMOTE)) ? "" : ":"
161 			);
162 	else
163 		sfprintf(sfstdout, "addr=\n");
164 }
165 
166 /*
167  * order by name
168  */
169 
170 static int
order(register Ftw_t * f1,register Ftw_t * f2)171 order(register Ftw_t* f1, register Ftw_t* f2)
172 {
173 	return f1->level == 3 ? strcoll(f1->name, f2->name) : 0;
174 }
175 
176 /*
177  * list the service mount directories
178  */
179 
180 #define PROC_OFF	4
181 #define SERVICE_COLS	37
182 
183 static int
list(register Ftw_t * ftw)184 list(register Ftw_t* ftw)
185 {
186 	register char*	s;
187 	register char*	t;
188 	register char*	u;
189 	register char*	p;
190 	char*		port;
191 	char*		proc;
192 	int		mode;
193 	int		n;
194 	uid_t		uid;
195 	struct stat	st;
196 
197 	static char	label[3][64];
198 	static char	qual_buf[64];
199 	static char	proc_buf[PATH_MAX + 1] = " -> ";
200 	static char	port_buf[PATH_MAX + 1];
201 	static char	serv_buf[PATH_MAX + 1];
202 	static char	time_buf[64];
203 
204 	static Sfio_t*	sp;
205 
206 	if (ftw->level > 0)
207 	{
208 		if (ftw->level > elementsof(label))
209 		{
210 			ftw->status = FTW_SKIP;
211 			mode = ftw->statb.st_mode & (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
212 			if (strmatch(ftw->name, "*-*-*-*"))
213 			{
214 				s = qual_buf;
215 				*s++ = '/';
216 				t = strrchr(ftw->name, '-');
217 				while (s < &qual_buf[sizeof(qual_buf) - 1] && (*s++ = *++t));
218 				*s = 0;
219 			}
220 			else
221 				qual_buf[0] = 0;
222 			if (!streq(label[1], "share"))
223 				mode |= (S_ISVTX|S_IXOTH);
224 			if (!sp && !(sp = sfstropen()))
225 				error(ERROR_SYSTEM|3, "out of space");
226 			sfprintf(sp, "%s/X%s", ftw->path, CS_MNT_TAIL);
227 			if (!(p = sfstruse(sp)))
228 				error(ERROR_SYSTEM|3, "out of space");
229 			s = p + ftw->pathlen + 1;
230 			*s = CS_MNT_PROCESS;
231 			if (pathgetlink(p, proc_buf + PROC_OFF, sizeof(proc_buf) - PROC_OFF) <= 0)
232 			{
233 				/*
234 				 * check for old single char cs mounts
235 				 */
236 
237 				*(s + 1) = 0;
238 				if (pathgetlink(p, proc_buf + PROC_OFF, sizeof(proc_buf) - PROC_OFF) <= 0)
239 				{
240 					*s = CS_MNT_STREAM;
241 					remove(p);
242 					*(s + 1) = CS_MNT_TAIL[0];
243 					remove(p);
244 					return 0;
245 				}
246 			}
247 			proc = proc_buf;
248 			if (strncmp(proc + PROC_OFF, "/n/", 3))
249 				u = proc + PROC_OFF;
250 			else
251 			{
252 				t = proc + PROC_OFF + 3;
253 				if (u = strchr(t, '/'))
254 				{
255 					*u = 0;
256 					if (strcmp(t, state.local))
257 					{
258 						*u = '/';
259 						u = 0;
260 					}
261 					else
262 						*u = '/';
263 				}
264 			}
265 			if (u && (n = strtol(u + 6, NiL, 10)) && kill(n, 0) && errno == ESRCH)
266 			{
267 				remove(p);
268 				*s = CS_MNT_STREAM;
269 				remove(p);
270 				return 0;
271 			}
272 			if (state.list & LIST_SERVICE)
273 			{
274 				*s = CS_MNT_STREAM;
275 				if (pathgetlink(p, port_buf, sizeof(port_buf)) > 0 && (port = strrchr(port_buf, '/')))
276 					port++;
277 				else
278 					port = "";
279 				if (stat(p, &st))
280 					st.st_mtime = ftw->statb.st_mtime;
281 				tmfmt(time_buf, sizeof(time_buf), "%?%QL", &st.st_mtime);
282 				*s = CS_MNT_LOG;
283 				if (stat(p, &st))
284 					st = ftw->statb;
285 				*(s - 1) = 0;
286 				sfprintf(sfstdout, "%c%s  1 %-8s %-8s %7s %s %s%s%s\n", label[0][0], fmtmode(mode, 0) + 1, fmtuid(st.st_uid), (mode & S_IROTH) ? "other" : fmtgid(ftw->statb.st_gid), port, time_buf, label[2], qual_buf, proc);
287 			}
288 			else
289 			{
290 				n = sfprintf(sfstdout, "/dev/%s/%s/%s", label[0], label[1], label[2]);
291 				if (!(mode & S_IROTH))
292 				{
293 					if (!(mode & S_IRGRP))
294 					{
295 						n += sfprintf(sfstdout, "/user");
296 						if (ftw->statb.st_uid != geteuid())
297 							n += sfprintf(sfstdout, "=%s", fmtuid(ftw->statb.st_uid));
298 					}
299 					else
300 					{
301 						n += sfprintf(sfstdout, "/group");
302 						if (ftw->statb.st_gid != getegid())
303 							n += sfprintf(sfstdout, "=%s", fmtgid(ftw->statb.st_gid));
304 					}
305 				}
306 				if (*ftw->name == '-')
307 					n += sfprintf(sfstdout, "/trust");
308 				else
309 				{
310 					sfsprintf(port_buf, sizeof(port_buf) - 1, "%s/%s/%s/%s%s", CS_SVC_DIR, label[0], label[2], label[2], CS_SVC_SUFFIX);
311 					uid = strtol(ftw->name, NiL, 0);
312 					if (!pathpath(port_buf, "", PATH_ABSOLUTE|PATH_EXECUTE, serv_buf, sizeof(serv_buf)) || stat(serv_buf, &st) || st.st_uid != uid)
313 						n += sfprintf(sfstdout, "/trust=%s", fmtuid(uid));
314 				}
315 				if (qual_buf[0])
316 					n += sfprintf(sfstdout, "%s", qual_buf);
317 				if (u && streq(label[1], "share"))
318 					n += sfprintf(sfstdout, "/local");
319 				if (*label[0] == 't')
320 				{
321 					*s = CS_MNT_AUTH;
322 					if (access(p, F_OK))
323 						n += sfprintf(sfstdout, "/other");
324 				}
325 				if (state.list &= ~LIST)
326 				{
327 					if ((state.list ^ (state.list>>1)) == (state.list | (state.list>>1)))
328 						while (n++ < SERVICE_COLS)
329 							sfputc(sfstdout, ' ');
330 					if (state.list & LIST_PROCESS)
331 					{
332 						*(s - 1) = 0;
333 						sfprintf(sfstdout, " %s", u ? u : proc + PROC_OFF);
334 					}
335 					if (state.list & LIST_MOUNT)
336 					{
337 						*(s - 1) = 0;
338 						sfprintf(sfstdout, " %s", p);
339 					}
340 				}
341 				sfprintf(sfstdout, "\n");
342 			}
343 		}
344 		else
345 		{
346 			s = ftw->name;
347 			if (ftw->level == 2 && streq(s, "share") && streq(s, state.local))
348 				s = "local";
349 			strncpy(label[ftw->level - 1], s, elementsof(label[0]) - 1);
350 		}
351 	}
352 	return 0;
353 }
354 
355 /*
356  * list messages in sp
357  * if continuous!=0 then act like tail -f
358  */
359 
360 static int
msgcat(register Sfio_t * sp,register int flags,unsigned long call,unsigned long terse)361 msgcat(register Sfio_t* sp, register int flags, unsigned long call, unsigned long terse)
362 {
363 	register long	n;
364 	Msg_call_t	msg;
365 
366 	if (flags & MSG_LIST_CONTINUOUS)
367 		sfset(sfstdout, SF_LINE, 1);
368 	for (;;)
369 	{
370 		while ((n = msgrecv(sffileno(sp), &msg)) > 0)
371 			if (MSG_MASK(msg.call) & call)
372 				msglist(sfstdout, &msg, flags, terse);
373 		if (n < 0)
374 			return -1;
375 		if (!(flags & MSG_LIST_CONTINUOUS))
376 			return 0;
377 		sleep(2);
378 	}
379 }
380 
381 int
main(int argc,char ** argv)382 main(int argc, char** argv)
383 {
384 	char**		ap;
385 	int		n;
386 	int		fd;
387 	int		hostenv = 0;
388 	int		initiate = CS_OPEN_READ;
389 	int		interactive = 0;
390 	int		msg = 0;
391 	int		clientflags = 0;
392 	int		remote = 0;
393 	int		translate = 0;
394 	unsigned long	call = ~0;
395 	unsigned long	terse = 0;
396 	char*		attr = 0;
397 	char*		host;
398 	char*		path;
399 	char*		proc;
400 	char*		sig = 0;
401 	char*		av[8];
402 	Sfio_t*		sp;
403 	char		buf[PATH_MAX + 1];
404 	char		tmp[PATH_MAX + 1];
405 
406 	NoP(argc);
407 	setlocale(LC_ALL, "");
408 	error_info.id = "cs";
409 	debug(systrace(0));
410 	for (;;)
411 	{
412 		switch (optget(argv, usage))
413 		{
414 		case 'a':
415 			attr = opt_info.arg;
416 			continue;
417 		case 'c':
418 			msg |= MSG_LIST;
419 			continue;
420 		case 'd':
421 			error_info.trace = -opt_info.num;
422 			continue;
423 		case 'f':
424 			msg |= MSG_LIST_CONTINUOUS;
425 			continue;
426 		case 'h':
427 			hostenv = 1;
428 			continue;
429 		case 'r':
430 			clientflags = CS_CLIENT_RAW;
431 			/*FALLTHROUGH*/
432 		case 'i':
433 			interactive = 1;
434 			msg |= MSG_LIST_ID;
435 			continue;
436 		case 'k':
437 			sig = opt_info.arg;
438 			continue;
439 		case 'l':
440 			state.list |= LIST;
441 			continue;
442 		case 'm':
443 			state.list |= LIST_MOUNT;
444 			continue;
445 		case 'p':
446 			state.list |= LIST_PROCESS;
447 			continue;
448 		case 'q':
449 			interactive = 1;
450 			initiate |= CS_OPEN_TEST;
451 			continue;
452 		case 's':
453 			state.list |= LIST_SERVICE;
454 			continue;
455 		case 't':
456 			translate = 1;
457 			continue;
458 		case 'C':
459 			call = msgsetmask(opt_info.arg);
460 			continue;
461 		case 'T':
462 			terse = msgsetmask(opt_info.arg);
463 			continue;
464 		case 'O':
465 			remote = 1;
466 			host = opt_info.arg;
467 			for (;;)
468 			{
469 				switch (*host++)
470 				{
471 				case 0:
472 					break;
473 				case CS_REMOTE_OPEN_AGENT:
474 					initiate |= CS_OPEN_AGENT;
475 					continue;
476 				case CS_REMOTE_OPEN_LOCAL:
477 					initiate |= CS_OPEN_LOCAL;
478 					continue;
479 				case CS_REMOTE_OPEN_NOW:
480 					initiate |= CS_OPEN_NOW;
481 					continue;
482 				case CS_REMOTE_OPEN_READ:
483 					continue;
484 				case CS_REMOTE_OPEN_SHARE:
485 					initiate |= CS_OPEN_SHARE;
486 					continue;
487 				case CS_REMOTE_OPEN_TEST:
488 					initiate |= CS_OPEN_TEST;
489 					continue;
490 				case CS_REMOTE_OPEN_TRUST:
491 					initiate |= CS_OPEN_TRUST;
492 					continue;
493 				default:
494 					error(2, "%c: unknown open flag", *(host - 1));
495 					break;
496 				}
497 				break;
498 			}
499 			continue;
500 		case '?':
501 			error(ERROR_USAGE|4, "%s", opt_info.arg);
502 			continue;
503 		case ':':
504 			error(2, "%s", opt_info.arg);
505 			continue;
506 		}
507 		break;
508 	}
509 	argv += opt_info.index;
510 	if (error_info.errors)
511 		error(ERROR_USAGE|4, "%s", optusage(NiL));
512 	if (msg & MSG_LIST)
513 	{
514 		if (!(path = *argv++))
515 			sp = sfstdin;
516 		else if (*argv)
517 			error(ERROR_USAGE|4, "%s", optusage(NiL));
518 		else if (!(sp = sfopen(NiL, path, "r")))
519 			error(ERROR_SYSTEM|3, "%s: cannot read", path);
520 		return msgcat(sp, msg, call, terse) != 0;
521 	}
522 	if (translate)
523 	{
524 		if (*argv)
525 			while (host = *argv++)
526 				address(host);
527 		else
528 		{
529 			sfopen(sfstdin, NiL, "rt");
530 			while (host = sfgetr(sfstdin, '\n', 1))
531 				address(host);
532 		}
533 		return 0;
534 	}
535 	state.local = csname(0);
536 	if ((path = *argv++) && path[0] == '-' && !path[1])
537 	{
538 		path = *argv++;
539 		initiate |= CS_OPEN_TEST;
540 	}
541 	if (remote)
542 	{
543 		if (!path)
544 			return 1;
545 		if (initiate & CS_OPEN_AGENT)
546 		{
547 			register char*	s = path;
548 			register char*	t = tmp;
549 			register int	n = 0;
550 
551 			/*
552 			 * get the unqualified-host connect stream path
553 			 */
554 
555 			while (*t++ = *s)
556 				switch (*s++)
557 				{
558 				case '/':
559 					while (*s == '/')
560 						s++;
561 					n++;
562 					break;
563 				case '.':
564 					if (n == 3)
565 						for (t--; *s && *s != '/'; s++);
566 					break;
567 				}
568 			path = tmp;
569 		}
570 		if ((fd = csopen(path, initiate)) < 0)
571 			return 1;
572 		if (initiate & CS_OPEN_AGENT)
573 		{
574 			*cs.control = CS_MNT_AUTH;
575 			remote = !access(cs.mount, F_OK);
576 			sfprintf(sfstdout, "%s%s\n", cspath(fd, 0), remote ? ".A" : "");
577 			if (remote)
578 			{
579 				close(fd);
580 				sfsync(sfstdout);
581 				if (csauth(-1, cs.mount, NiL))
582 					return 1;
583 			}
584 		}
585 	}
586 	else if (sig)
587 	{
588 		if (path)
589 		{
590 			do
591 			{
592 				if ((fd = csopen(path, CS_OPEN_TEST)) < 0)
593 				{
594 					error(ERROR_SYSTEM|2, "%s: cannot open connect stream", path);
595 					continue;
596 				}
597 				close(fd);
598 				if (cs.flags & CS_ADDR_REMOTE)
599 					host = cs.host;
600 				else
601 				{
602 					*cs.control = CS_MNT_PROCESS;
603 					if (pathgetlink(cs.mount, buf, sizeof(buf)) <= 0)
604 					{
605 						error(ERROR_SYSTEM|2, "%s: cannot get service process mount", path);
606 						continue;
607 					}
608 					if (tokscan(buf, NiL, "/proc/%s", &proc) == 1)
609 						host = 0;
610 					else if (tokscan(buf, NiL, "/n/%s/proc/%s", &host, &proc) != 2)
611 					{
612 						error(2, "%s: %s: invalid service process mount", path, buf);
613 						continue;
614 					}
615 				}
616 				sfsprintf(tmp, sizeof(tmp), "-%s", sig);
617 				ap = av;
618 				if (host && !streq(host, state.local))
619 				{
620 					*ap++ = CS_REMOTE_SHELL;
621 					*ap++ = host;
622 					if (*cs.user)
623 					{
624 						*ap++ = "-l";
625 						*ap++ = cs.user;
626 					}
627 				}
628 				*ap++ = "kill";
629 				*ap++ = tmp;
630 				*ap++ = proc;
631 				*ap = 0;
632 				if (procclose(procopen(av[0], av, NiL, NiL, PROC_UID|PROC_GID|(*argv ? PROC_OVERLAY : 0))))
633 					error(ERROR_SYSTEM|2, "%s: cannot %s %s to kill server", path, av[0], host);
634 			} while (path = *argv++);
635 		}
636 	}
637 	else if (state.list & LIST)
638 	{
639 		if (path)
640 			error(1, "%s: argument not expected", path);
641 		ap = av;
642 		*ap++ = csvar(CS_VAR_LOCAL, 0);
643 		if (pathpath(csvar(CS_VAR_SHARE, 0), "", PATH_EXECUTE, tmp, sizeof(tmp)))
644 			*ap++ = tmp;
645 		*ap = 0;
646 		ftwalk((char*)av, list, FTW_MULTIPLE|FTW_PHYSICAL, order);
647 	}
648 	else if (attr)
649 	{
650 		if (!path)
651 		{
652 			while (proc = csattr(NiL, attr))
653 				sfputr(sfstdout, proc, '\n');
654 		}
655 		else
656 		{
657 			n = *argv != 0;
658 			hostenv = streq(attr, "-");
659 			terse = streq(attr, "name");
660 			do
661 			{
662 				if (proc = csattr(path, attr))
663 				{
664 					if (hostenv)
665 						sfputr(sfstdout, "name", '=');
666 					if (!terse && (hostenv || n))
667 						sfputr(sfstdout, path, ' ');
668 					sfputr(sfstdout, proc, '\n');
669 				}
670 				else if (streq(path, CS_HOST_SHARE) && (sp = csinfo(path, NiL)))
671 				{
672 					while (path = sfgetr(sp, '\n', 1))
673 						if (proc = csattr(path, attr))
674 						{
675 							if (hostenv)
676 								sfputr(sfstdout, "name", '=');
677 							if (!terse)
678 								sfputr(sfstdout, path, ' ');
679 							sfputr(sfstdout, proc, '\n');
680 						}
681 						else
682 							error(2, "%s: no host info", path);
683 					sfclose(sp);
684 				}
685 				else
686 					error(2, "%s: no host info", path);
687 			} while (path = *argv++);
688 		}
689 		return 0;
690 	}
691 	else if (hostenv)
692 	{
693 		if (!path || streq(path, "local"))
694 			path = state.local;
695 		proc = csattr(path, "type");
696 		sfprintf(sfstdout, "%s=%s %s=%s\n", CO_ENV_HOST, path, CO_ENV_TYPE, proc ? proc : "unknown");
697 		return 0;
698 	}
699 	else if (path)
700 	{
701 		if ((fd = csopen(path, initiate)) < 0)
702 			error(ERROR_SYSTEM|3, "%s: cannot open connect stream", path);
703 		if (state.list & LIST_MOUNT)
704 		{
705 			if (*argv)
706 				error(1, "%s: argument not expected", path);
707 			if (cs.flags & CS_ADDR_REMOTE)
708 				error(1, "%s: remote connect stream", path);
709 			else
710 			{
711 				*(cs.control - 1) = 0;
712 				sfputr(sfstdout, cs.mount, '\n');
713 			}
714 		}
715 		else if (interactive)
716 			return csclient(fd, path, "cs> ", NiL, clientflags) || error_info.errors;
717 		else
718 		{
719 			if (*argv)
720 			{
721 				close(0);
722 				close(1);
723 				if (dup(fd) != 0 || dup(fd) != 1)
724 					error(ERROR_SYSTEM|3, "%s: cannot redirect connect stream", path);
725 				close(fd);
726 				if (!(initiate & CS_OPEN_TEST) && csdaemon((1<<0)|(1<<1)))
727 					error(ERROR_SYSTEM|3, "%s: cannot dive into background", path);
728 				procopen(*argv, argv, NiL, NiL, PROC_OVERLAY|PROC_UID|PROC_GID);
729 				error(ERROR_SYSTEM|4, "%s: %s: cannot execute", path, *argv);
730 			}
731 			sfprintf(sfstdout, "%s\n", cspath(fd, 0));
732 		}
733 	}
734 	else if (interactive)
735 		error(3, "connect stream argument expected");
736 	else
737 		sfprintf(sfstdout, "%s\n", cspath(0, 0));
738 	return error_info.errors != 0;
739 }
740