• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * libwebsockets - small server side websockets and web server implementation
3  *
4  * Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22  * IN THE SOFTWARE.
23  */
24 
25 #if !defined(_GNU_SOURCE)
26 #define _GNU_SOURCE
27 #endif
28 
29 #include "private-lib-core.h"
30 #include <unistd.h>
31 
32 #if defined(__OpenBSD__) || defined(__NetBSD__)
33 #include <sys/resource.h>
34 #include <sys/wait.h>
35 #endif
36 
37 void
lws_spawn_timeout(struct lws_sorted_usec_list * sul)38 lws_spawn_timeout(struct lws_sorted_usec_list *sul)
39 {
40 	struct lws_spawn_piped *lsp = lws_container_of(sul,
41 					struct lws_spawn_piped, sul);
42 
43 	lwsl_warn("%s: spawn exceeded timeout, killing\n", __func__);
44 
45 	lws_spawn_piped_kill_child_process(lsp);
46 }
47 
48 void
lws_spawn_sul_reap(struct lws_sorted_usec_list * sul)49 lws_spawn_sul_reap(struct lws_sorted_usec_list *sul)
50 {
51 	struct lws_spawn_piped *lsp = lws_container_of(sul,
52 					struct lws_spawn_piped, sul_reap);
53 
54 	lwsl_notice("%s: reaping spawn after last stdpipe, tries left %d\n",
55 		    __func__, lsp->reap_retry_budget);
56 	if (!lws_spawn_reap(lsp) && !lsp->pipes_alive) {
57 		if (--lsp->reap_retry_budget) {
58 			lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi,
59 					 &lsp->sul_reap, lws_spawn_sul_reap,
60 					 250 * LWS_US_PER_MS);
61 		} else {
62 			lwsl_err("%s: Unable to reap lsp %p, killing\n",
63 				 __func__, lsp);
64 			lsp->reap_retry_budget = 20;
65 			lws_spawn_piped_kill_child_process(lsp);
66 		}
67 	}
68 }
69 
70 static struct lws *
lws_create_stdwsi(struct lws_context * context,int tsi,const struct lws_role_ops * ops)71 lws_create_stdwsi(struct lws_context *context, int tsi,
72 		     const struct lws_role_ops *ops)
73 {
74 	struct lws_context_per_thread *pt = &context->pt[tsi];
75 	struct lws *new_wsi;
76 
77 	if (!context->vhost_list)
78 		return NULL;
79 
80 	if ((unsigned int)pt->fds_count == context->fd_limit_per_thread - 1) {
81 		lwsl_err("no space for new conn\n");
82 		return NULL;
83 	}
84 
85 	lws_context_lock(context, __func__);
86 	new_wsi = __lws_wsi_create_with_role(context, tsi, ops, NULL);
87 	lws_context_unlock(context);
88 	if (new_wsi == NULL) {
89 		lwsl_err("Out of memory for new connection\n");
90 		return NULL;
91 	}
92 
93 	new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
94 
95 	/* initialize the instance struct */
96 
97 	lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, ops);
98 
99 	new_wsi->hdr_parsing_completed = 0;
100 
101 	/*
102 	 * these can only be set once the protocol is known
103 	 * we set an unestablished connection's protocol pointer
104 	 * to the start of the defauly vhost supported list, so it can look
105 	 * for matching ones during the handshake
106 	 */
107 
108 	new_wsi->user_space = NULL;
109 
110 	return new_wsi;
111 }
112 
113 void
lws_spawn_piped_destroy(struct lws_spawn_piped ** _lsp)114 lws_spawn_piped_destroy(struct lws_spawn_piped **_lsp)
115 {
116 	struct lws_spawn_piped *lsp = *_lsp;
117 	int n;
118 
119 	if (!lsp)
120 		return;
121 
122 	lws_dll2_remove(&lsp->dll);
123 
124 	lws_sul_cancel(&lsp->sul);
125 	lws_sul_cancel(&lsp->sul_reap);
126 
127 	for (n = 0; n < 3; n++) {
128 #if 0
129 		if (lsp->pipe_fds[n][!!(n == 0)] == 0)
130 			lwsl_err("ZERO FD IN CGI CLOSE");
131 
132 		if (lsp->pipe_fds[n][!!(n == 0)] >= 0) {
133 			close(lsp->pipe_fds[n][!!(n == 0)]);
134 			lsp->pipe_fds[n][!!(n == 0)] = LWS_SOCK_INVALID;
135 		}
136 #endif
137 		if (lsp->stdwsi[n]) {
138 			lws_set_timeout(lsp->stdwsi[n], 1, LWS_TO_KILL_ASYNC);
139 			lsp->stdwsi[n] = NULL;
140 		}
141 	}
142 
143 	lws_free_set_NULL((*_lsp));
144 }
145 
146 int
lws_spawn_reap(struct lws_spawn_piped * lsp)147 lws_spawn_reap(struct lws_spawn_piped *lsp)
148 {
149 	long hz = sysconf(_SC_CLK_TCK); /* accounting Hz */
150 	void *opaque = lsp->info.opaque;
151 	lsp_cb_t cb = lsp->info.reap_cb;
152 	struct lws_spawn_piped temp;
153 	struct tms tms;
154 #if defined(__OpenBSD__) || defined(__NetBSD__)
155 	struct rusage rusa;
156 	int status;
157 #endif
158 	int n;
159 
160 	if (lsp->child_pid < 1)
161 		return 0;
162 
163 	/* check if exited, do not reap yet */
164 
165 	memset(&lsp->si, 0, sizeof(lsp->si));
166 #if defined(__OpenBSD__) || defined(__NetBSD__)
167 	n = wait4(lsp->child_pid, &status, WNOHANG, &rusa);
168 	if (!n)
169 		return 0;
170 	lsp->si.si_code = WIFEXITED(status);
171 #else
172 	n = waitid(P_PID, (id_t)lsp->child_pid, &lsp->si, WEXITED | WNOHANG | WNOWAIT);
173 #endif
174 	if (n < 0) {
175 		lwsl_info("%s: child %d still running\n", __func__, lsp->child_pid);
176 		return 0;
177 	}
178 
179 	if (!lsp->si.si_code)
180 		return 0;
181 
182 	/* his process has exited... */
183 
184 	if (!lsp->reaped) {
185 		/* mark the earliest time we knew he had gone */
186 		lsp->reaped = lws_now_usecs();
187 
188 		/*
189 		 * Switch the timeout to restrict the amount of grace time
190 		 * to drain stdwsi
191 		 */
192 
193 		lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi,
194 				 &lsp->sul, lws_spawn_timeout,
195 				 5 * LWS_US_PER_SEC);
196 	}
197 
198 	/*
199 	 * Stage finalizing our reaction to the process going down until the
200 	 * stdwsi flushed whatever is in flight and all noticed they were
201 	 * closed.  For that reason, each stdwsi close must call lws_spawn_reap
202 	 * to check if that was the last one and we can proceed with the reap.
203 	 */
204 
205 	if (!lsp->ungraceful && lsp->pipes_alive) {
206 		lwsl_info("%s: %d stdwsi alive, not reaping\n", __func__,
207 				lsp->pipes_alive);
208 		return 0;
209 	}
210 
211 	/* we reached the reap point, no need for timeout wait */
212 
213 	lws_sul_cancel(&lsp->sul);
214 
215 	/*
216 	 * All the stdwsi went down, nothing more is coming... it's over
217 	 * Collect the final information and then reap the dead process
218 	 */
219 
220 	if (times(&tms) != (clock_t) -1) {
221 		/*
222 		 * Cpu accounting in us
223 		 */
224 		lsp->accounting[0] = (lws_usec_t)((uint64_t)tms.tms_cstime * 1000000) / hz;
225 		lsp->accounting[1] = (lws_usec_t)((uint64_t)tms.tms_cutime * 1000000) / hz;
226 		lsp->accounting[2] = (lws_usec_t)((uint64_t)tms.tms_stime * 1000000) / hz;
227 		lsp->accounting[3] = (lws_usec_t)((uint64_t)tms.tms_utime * 1000000) / hz;
228 	}
229 
230 	temp = *lsp;
231 #if defined(__OpenBSD__) || defined(__NetBSD__)
232 	n = wait4(lsp->child_pid, &status, WNOHANG, &rusa);
233 	if (!n)
234 		return 0;
235 	lsp->si.si_code = WIFEXITED(status);
236 	if (lsp->si.si_code == CLD_EXITED)
237 		temp.si.si_code = CLD_EXITED;
238 	temp.si.si_status = WEXITSTATUS(status);
239 #else
240 	n = waitid(P_PID, (id_t)lsp->child_pid, &temp.si, WEXITED | WNOHANG);
241 #endif
242 	temp.si.si_status &= 0xff; /* we use b8 + for flags */
243 	lwsl_info("%s: waitd says %d, process exit %d\n",
244 		    __func__, n, temp.si.si_status);
245 
246 	lsp->child_pid = -1;
247 
248 	/* destroy the lsp itself first (it's freed and plsp set NULL */
249 
250 	if (lsp->info.plsp)
251 		lws_spawn_piped_destroy(lsp->info.plsp);
252 
253 	/* then do the parent callback informing it's destroyed */
254 
255 	if (cb)
256 		cb(opaque, temp.accounting, &temp.si,
257 		   temp.we_killed_him_timeout |
258 			   (temp.we_killed_him_spew << 1));
259 
260 	return 1; /* was reaped */
261 }
262 
263 int
lws_spawn_piped_kill_child_process(struct lws_spawn_piped * lsp)264 lws_spawn_piped_kill_child_process(struct lws_spawn_piped *lsp)
265 {
266 	int status, n;
267 
268 	if (lsp->child_pid <= 0)
269 		return 1;
270 
271 	lsp->ungraceful = 1; /* don't wait for flushing, just kill it */
272 
273 	if (lws_spawn_reap(lsp))
274 		/* that may have invalidated lsp */
275 		return 0;
276 
277 	/* kill the process group */
278 	n = kill(-lsp->child_pid, SIGTERM);
279 	lwsl_debug("%s: SIGTERM child PID %d says %d (errno %d)\n", __func__,
280 		   lsp->child_pid, n, errno);
281 	if (n < 0) {
282 		/*
283 		 * hum seen errno=3 when process is listed in ps,
284 		 * it seems we don't always retain process grouping
285 		 *
286 		 * Direct these fallback attempt to the exact child
287 		 */
288 		n = kill(lsp->child_pid, SIGTERM);
289 		if (n < 0) {
290 			n = kill(lsp->child_pid, SIGPIPE);
291 			if (n < 0) {
292 				n = kill(lsp->child_pid, SIGKILL);
293 				if (n < 0)
294 					lwsl_info("%s: SIGKILL PID %d "
295 						 "failed errno %d "
296 						 "(maybe zombie)\n", __func__,
297 						 lsp->child_pid, errno);
298 			}
299 		}
300 	}
301 
302 	/* He could be unkillable because he's a zombie */
303 
304 	n = 1;
305 	while (n > 0) {
306 		n = waitpid(-lsp->child_pid, &status, WNOHANG);
307 		if (n > 0)
308 			lwsl_debug("%s: reaped PID %d\n", __func__, n);
309 		if (n <= 0) {
310 			n = waitpid(lsp->child_pid, &status, WNOHANG);
311 			if (n > 0)
312 				lwsl_debug("%s: reaped PID %d\n", __func__, n);
313 		}
314 	}
315 
316 	lws_spawn_reap(lsp);
317 	/* that may have invalidated lsp */
318 
319 	return 0;
320 }
321 
322 /*
323  * Deals with spawning a subprocess and executing it securely with stdin/out/err
324  * diverted into pipes
325  */
326 
327 struct lws_spawn_piped *
lws_spawn_piped(const struct lws_spawn_piped_info * i)328 lws_spawn_piped(const struct lws_spawn_piped_info *i)
329 {
330 	const struct lws_protocols *pcol = i->vh->context->vhost_list->protocols;
331 	struct lws_context *context = i->vh->context;
332 	struct lws_spawn_piped *lsp;
333 	const char *wd;
334 	int n, m;
335 
336 	if (i->protocol_name)
337 		pcol = lws_vhost_name_to_protocol(i->vh, i->protocol_name);
338 	if (!pcol) {
339 		lwsl_err("%s: unknown protocol %s\n", __func__,
340 			 i->protocol_name ? i->protocol_name : "default");
341 
342 		return NULL;
343 	}
344 
345 	lsp = lws_zalloc(sizeof(*lsp), __func__);
346 	if (!lsp)
347 		return NULL;
348 
349 	/* wholesale take a copy of info */
350 	lsp->info = *i;
351 	lsp->reap_retry_budget = 20;
352 
353 	/*
354 	 * Prepare the stdin / out / err pipes
355 	 */
356 
357 	for (n = 0; n < 3; n++) {
358 		lsp->pipe_fds[n][0] = -1;
359 		lsp->pipe_fds[n][1] = -1;
360 	}
361 
362 	/* create pipes for [stdin|stdout] and [stderr] */
363 
364 	for (n = 0; n < 3; n++) {
365 		if (pipe(lsp->pipe_fds[n]) == -1)
366 			goto bail1;
367 		lws_plat_apply_FD_CLOEXEC(lsp->pipe_fds[n][n == 0]);
368 	}
369 
370 	/*
371 	 * At this point, we have 6 pipe fds open on lws side and no wsis
372 	 * bound to them
373 	 */
374 
375 	/* create wsis for each stdin/out/err fd */
376 
377 	for (n = 0; n < 3; n++) {
378 		lsp->stdwsi[n] = lws_create_stdwsi(i->vh->context, i->tsi,
379 					  i->ops ? i->ops : &role_ops_raw_file);
380 		if (!lsp->stdwsi[n]) {
381 			lwsl_err("%s: unable to create lsp stdwsi\n", __func__);
382 			goto bail2;
383 		}
384 
385                 __lws_lc_tag(i->vh->context, &i->vh->context->lcg[LWSLCG_WSI],
386                 	     &lsp->stdwsi[n]->lc, "nspawn-stdwsi-%d", n);
387 
388 		lsp->stdwsi[n]->lsp_channel = (uint8_t)n;
389 		lws_vhost_bind_wsi(i->vh, lsp->stdwsi[n]);
390 		lsp->stdwsi[n]->a.protocol = pcol;
391 		lsp->stdwsi[n]->a.opaque_user_data = i->opaque;
392 
393 		lwsl_debug("%s: lsp stdwsi %p: pipe idx %d -> fd %d / %d\n", __func__,
394 			   lsp->stdwsi[n], n, lsp->pipe_fds[n][n == 0],
395 			   lsp->pipe_fds[n][n != 0]);
396 
397 		/* read side is 0, stdin we want the write side, others read */
398 
399 		lsp->stdwsi[n]->desc.sockfd = lsp->pipe_fds[n][n == 0];
400 		if (fcntl(lsp->pipe_fds[n][n == 0], F_SETFL, O_NONBLOCK) < 0) {
401 			lwsl_err("%s: setting NONBLOCK failed\n", __func__);
402 			goto bail2;
403 		}
404 
405 		/*
406 		 * We have bound 3 x pipe fds to wsis, wr side of stdin and rd
407 		 * side of stdout / stderr... those are marked CLOEXEC so they
408 		 * won't go through the fork
409 		 *
410 		 * rd side of stdin and wr side of stdout / stderr are open but
411 		 * not bound to anything on lws side.
412 		 */
413 	}
414 
415 	/*
416 	 * Stitch the wsi fd into the poll wait
417 	 */
418 
419 	for (n = 0; n < 3; n++) {
420 		if (context->event_loop_ops->sock_accept)
421 			if (context->event_loop_ops->sock_accept(lsp->stdwsi[n]))
422 				goto bail3;
423 
424 		if (__insert_wsi_socket_into_fds(context, lsp->stdwsi[n]))
425 			goto bail3;
426 		if (i->opt_parent) {
427 			lsp->stdwsi[n]->parent = i->opt_parent;
428 			lsp->stdwsi[n]->sibling_list = i->opt_parent->child_list;
429 			i->opt_parent->child_list = lsp->stdwsi[n];
430 		}
431 	}
432 
433 	if (lws_change_pollfd(lsp->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT))
434 		goto bail3;
435 	if (lws_change_pollfd(lsp->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN))
436 		goto bail3;
437 	if (lws_change_pollfd(lsp->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN))
438 		goto bail3;
439 
440 	lwsl_info("%s: fds in %d, out %d, err %d\n", __func__,
441 		   lsp->stdwsi[LWS_STDIN]->desc.sockfd,
442 		   lsp->stdwsi[LWS_STDOUT]->desc.sockfd,
443 		   lsp->stdwsi[LWS_STDERR]->desc.sockfd);
444 
445 	/* we are ready with the redirection pipes... do the (v)fork */
446 #if defined(__sun) || !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
447 	lsp->child_pid = fork();
448 #else
449 	lsp->child_pid = vfork();
450 #endif
451 	if (lsp->child_pid < 0) {
452 		lwsl_err("%s: fork failed, errno %d", __func__, errno);
453 		goto bail3;
454 	}
455 
456 #if defined(__linux__)
457 	if (!lsp->child_pid)
458 		prctl(PR_SET_PDEATHSIG, SIGTERM);
459 #endif
460 
461 	if (lsp->info.disable_ctrlc)
462 		/* stops non-daemonized main processess getting SIGINT
463 		 * from TTY */
464 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
465 		setpgid(0, 0);
466 #else
467 		setpgrp();
468 #endif
469 
470 	if (lsp->child_pid) {
471 
472 		/*
473 		 * We are the parent process.  We can close our copy of the
474 		 * "other" side of the pipe fds, ie, rd for stdin and wr for
475 		 * stdout / stderr.
476 		 */
477 		for (n = 0; n < 3; n++)
478 			/* these guys didn't have any wsi footprint */
479 			close(lsp->pipe_fds[n][n != 0]);
480 
481 		lsp->pipes_alive = 3;
482 		lsp->created = lws_now_usecs();
483 
484 		lwsl_info("%s: lsp %p spawned PID %d\n", __func__, lsp,
485 			  lsp->child_pid);
486 
487 		lws_sul_schedule(context, i->tsi, &lsp->sul, lws_spawn_timeout,
488 				 i->timeout_us ? i->timeout_us :
489 						   300 * LWS_US_PER_SEC);
490 
491 		if (i->owner)
492 			lws_dll2_add_head(&lsp->dll, i->owner);
493 
494 		if (i->timeout_us)
495 			lws_sul_schedule(context, i->tsi, &lsp->sul,
496 					 lws_spawn_timeout, i->timeout_us);
497 
498 		return lsp;
499 	}
500 
501 	/*
502 	 * We are the forked process, redirect and kill inherited things.
503 	 *
504 	 * Because of vfork(), we cannot do anything that changes pages in
505 	 * the parent environment.  Stuff that changes kernel state for the
506 	 * process is OK.  Stuff that happens after the execvpe() is OK.
507 	 */
508 
509 	if (i->chroot_path && chroot(i->chroot_path)) {
510 		lwsl_err("%s: child chroot %s failed, errno %d\n",
511 			 __func__, i->chroot_path, errno);
512 
513 		exit(2);
514 	}
515 
516 	/* cwd: somewhere we can at least read things and enter it */
517 
518 	wd = i->wd;
519 	if (!wd)
520 		wd = "/tmp";
521 	if (chdir(wd))
522 		lwsl_notice("%s: Failed to cd to %s\n", __func__, wd);
523 
524 	/*
525 	 * Bind the child's stdin / out / err to its side of our pipes
526 	 */
527 
528 	for (m = 0; m < 3; m++) {
529 		if (dup2(lsp->pipe_fds[m][m != 0], m) < 0) {
530 			lwsl_err("%s: stdin dup2 failed\n", __func__);
531 			goto bail3;
532 		}
533 		/*
534 		 * CLOEXEC on the lws-side of the pipe fds should have already
535 		 * dealt with closing those for the child perspective.
536 		 *
537 		 * Now it has done the dup, the child should close its original
538 		 * copies of its side of the pipes.
539 		 */
540 
541 		close(lsp->pipe_fds[m][m != 0]);
542 	}
543 
544 #if defined(__sun) || !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
545 #if defined(__linux__) || defined(__APPLE__) || defined(__sun)
546 	m = 0;
547 	while (i->env_array[m]){
548 		const char *p = strchr(i->env_array[m], '=');
549 		int naml = lws_ptr_diff(p, i->env_array[m]);
550 		char enam[32];
551 
552 		lws_strnncpy(enam, i->env_array[m], naml, sizeof(enam));
553 		setenv(enam, p, 1);
554 		m++;
555 	}
556 #endif
557 	execvp(i->exec_array[0], (char * const *)&i->exec_array[0]);
558 #else
559 	execvpe(i->exec_array[0], (char * const *)&i->exec_array[0],
560 		(char **)&i->env_array[0]);
561 #endif
562 
563 	lwsl_err("%s: child exec of %s failed %d\n", __func__, i->exec_array[0],
564 		 LWS_ERRNO);
565 
566 	_exit(1);
567 
568 bail3:
569 
570 	while (--n >= 0)
571 		__remove_wsi_socket_from_fds(lsp->stdwsi[n]);
572 bail2:
573 	for (n = 0; n < 3; n++)
574 		if (lsp->stdwsi[n])
575 			__lws_free_wsi(lsp->stdwsi[n]);
576 
577 bail1:
578 	for (n = 0; n < 3; n++) {
579 		if (lsp->pipe_fds[n][0] >= 0)
580 			close(lsp->pipe_fds[n][0]);
581 		if (lsp->pipe_fds[n][1] >= 0)
582 			close(lsp->pipe_fds[n][1]);
583 	}
584 
585 	lws_free(lsp);
586 
587 	lwsl_err("%s: failed\n", __func__);
588 
589 	return NULL;
590 }
591 
592 void
lws_spawn_stdwsi_closed(struct lws_spawn_piped * lsp,struct lws * wsi)593 lws_spawn_stdwsi_closed(struct lws_spawn_piped *lsp, struct lws *wsi)
594 {
595 	int n;
596 
597 	assert(lsp);
598 	lsp->pipes_alive--;
599 	lwsl_debug("%s: pipes alive %d\n", __func__, lsp->pipes_alive);
600 	if (!lsp->pipes_alive)
601 		lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi,
602 				 &lsp->sul_reap, lws_spawn_sul_reap, 1);
603 
604 	for (n = 0; n < 3; n++)
605 		if (lsp->stdwsi[n] == wsi)
606 			lsp->stdwsi[n] = NULL;
607 }
608 
609 int
lws_spawn_get_stdfd(struct lws * wsi)610 lws_spawn_get_stdfd(struct lws *wsi)
611 {
612 	return wsi->lsp_channel;
613 }
614