1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <stdio.h>
6 #include <string.h>
7 #include <threads.h>
8
9 #include <ddk/device.h>
10 #include <ddk/driver.h>
11 #include "pty-core.h"
12 #include "pty-fifo.h"
13
14 #include <zircon/errors.h>
15 #include <zircon/device/pty.h>
16
17 #if 0
18 #define xprintf(fmt...) printf(fmt)
19 #else
20 #define xprintf(fmt...) do {} while (0)
21 #endif
22
23 #define CTRL_(n) ((n) - 'A' + 1)
24
25 #define CTRL_C CTRL_('C')
26 #define CTRL_S CTRL_('S')
27 #define CTRL_Z CTRL_('Z')
28
29 #define PTY_CLI_RAW_MODE (0x00000001u)
30
31 #define PTY_CLI_CONTROL (0x00010000u)
32 #define PTY_CLI_ACTIVE (0x00020000u)
33 #define PTY_CLI_PEER_CLOSED (0x00040000u)
34
35 struct pty_client {
36 zx_device_t* zxdev;
37 pty_server_t* srv;
38 uint32_t id;
39 uint32_t flags;
40 pty_fifo_t fifo;
41 list_node_t node;
42 };
43
44 static zx_status_t pty_openat(pty_server_t* ps, zx_device_t** out, uint32_t id, uint32_t flags);
45
46
47
48 // pty client device operations
49
pty_client_read(void * ctx,void * buf,size_t count,zx_off_t off,size_t * actual)50 static zx_status_t pty_client_read(void* ctx, void* buf, size_t count, zx_off_t off,
51 size_t* actual) {
52 pty_client_t* pc = ctx;
53 pty_server_t* ps = pc->srv;
54
55 mtx_lock(&ps->lock);
56 bool was_full = pty_fifo_is_full(&pc->fifo);
57 size_t length = pty_fifo_read(&pc->fifo, buf, count);
58 if (pty_fifo_is_empty(&pc->fifo)) {
59 device_state_clr(pc->zxdev, DEV_STATE_READABLE);
60 }
61 if (was_full && length) {
62 device_state_set(ps->zxdev, DEV_STATE_WRITABLE);
63 }
64 mtx_unlock(&ps->lock);
65
66 if (length > 0) {
67 *actual =length;
68 return ZX_OK;
69 } else {
70 return (pc->flags & PTY_CLI_PEER_CLOSED) ? ZX_ERR_PEER_CLOSED : ZX_ERR_SHOULD_WAIT;
71 }
72 }
73
pty_client_write(void * ctx,const void * buf,size_t count,zx_off_t off,size_t * actual)74 static zx_status_t pty_client_write(void* ctx, const void* buf, size_t count, zx_off_t off,
75 size_t* actual) {
76 pty_client_t* pc = ctx;
77 pty_server_t* ps = pc->srv;
78
79 ssize_t r;
80
81 mtx_lock(&ps->lock);
82 if (pc->flags & PTY_CLI_ACTIVE) {
83 size_t length;
84 r = ps->recv(ps, buf, count, &length);
85 if (r == ZX_OK) {
86 *actual = length;
87 } else if (r == ZX_ERR_SHOULD_WAIT) {
88 device_state_clr(pc->zxdev, DEV_STATE_WRITABLE);
89 }
90 } else {
91 r = (pc->flags & PTY_CLI_PEER_CLOSED) ? ZX_ERR_PEER_CLOSED : ZX_ERR_SHOULD_WAIT;
92 }
93 mtx_unlock(&ps->lock);
94
95 return r;
96 }
97
98 // mask of invalid features
99 #define PTY_FEATURE_BAD (~PTY_FEATURE_RAW)
100
pty_make_active_locked(pty_server_t * ps,pty_client_t * pc)101 static void pty_make_active_locked(pty_server_t* ps, pty_client_t* pc) {
102 xprintf("pty cli %p (id=%u) becomes active\n", pc, pc->id);
103 if (ps->active != pc) {
104 if (ps->active) {
105 ps->active->flags &= (~PTY_CLI_ACTIVE);
106 device_state_clr(ps->active->zxdev, DEV_STATE_WRITABLE);
107 }
108 ps->active = pc;
109 pc->flags |= PTY_CLI_ACTIVE;
110 device_state_set(pc->zxdev, DEV_STATE_WRITABLE);
111 if (pty_fifo_is_full(&pc->fifo)) {
112 device_state_clr_set(ps->zxdev, DEV_STATE_WRITABLE | DEV_STATE_HANGUP, 0);
113 } else {
114 device_state_clr_set(ps->zxdev, DEV_STATE_HANGUP, DEV_STATE_WRITABLE);
115 }
116 }
117 }
118
pty_adjust_signals_locked(pty_client_t * pc)119 static void pty_adjust_signals_locked(pty_client_t* pc) {
120 uint32_t set = 0;
121 uint32_t clr = 0;
122 if (pc->flags & PTY_CLI_ACTIVE) {
123 set = DEV_STATE_WRITABLE;
124 } else {
125 clr = DEV_STATE_WRITABLE;
126 }
127 if (pty_fifo_is_empty(&pc->fifo)) {
128 clr = DEV_STATE_READABLE;
129 } else {
130 set = DEV_STATE_READABLE;
131 }
132 device_state_clr_set(pc->zxdev, clr, set);
133 }
134
135
pty_client_ioctl(void * ctx,uint32_t op,const void * in_buf,size_t in_len,void * out_buf,size_t out_len,size_t * out_actual)136 static zx_status_t pty_client_ioctl(void* ctx, uint32_t op,
137 const void* in_buf, size_t in_len,
138 void* out_buf, size_t out_len, size_t* out_actual) {
139 pty_client_t* pc = ctx;
140 pty_server_t* ps = pc->srv;
141
142 switch (op) {
143 case IOCTL_PTY_CLR_SET_FEATURE: {
144 const pty_clr_set_t* cs = in_buf;
145 if ((in_len != sizeof(pty_clr_set_t)) ||
146 (cs->clr & PTY_FEATURE_BAD) ||
147 (cs->set & PTY_FEATURE_BAD)) {
148 return ZX_ERR_INVALID_ARGS;
149 }
150 mtx_lock(&ps->lock);
151 pc->flags = (pc->flags & (~cs->clr)) | cs->set;
152 mtx_unlock(&ps->lock);
153 return ZX_OK;
154 }
155 case IOCTL_PTY_GET_WINDOW_SIZE: {
156 pty_window_size_t* wsz = out_buf;
157 if (out_len != sizeof(pty_window_size_t)) {
158 return ZX_ERR_INVALID_ARGS;
159 }
160 mtx_lock(&ps->lock);
161 wsz->width = ps->width;
162 wsz->height = ps->height;
163 mtx_unlock(&ps->lock);
164 *out_actual = sizeof(pty_window_size_t);
165 return ZX_OK;
166 }
167 case IOCTL_PTY_MAKE_ACTIVE: {
168 if (in_len != sizeof(uint32_t)) {
169 return ZX_ERR_INVALID_ARGS;
170 }
171 if (!(pc->flags & PTY_CLI_CONTROL)) {
172 return ZX_ERR_ACCESS_DENIED;
173 }
174 uint32_t id = *((uint32_t*)in_buf);
175 mtx_lock(&ps->lock);
176 pty_client_t* c;
177 list_for_every_entry(&ps->clients, c, pty_client_t, node) {
178 if (c->id == id) {
179 pty_make_active_locked(ps, c);
180 mtx_unlock(&ps->lock);
181 return ZX_OK;
182 }
183 }
184 mtx_unlock(&ps->lock);
185 return ZX_ERR_NOT_FOUND;
186 }
187 case IOCTL_PTY_READ_EVENTS: {
188 if (!(pc->flags & PTY_CLI_CONTROL)) {
189 return ZX_ERR_ACCESS_DENIED;
190 }
191 if (out_len != sizeof(uint32_t)) {
192 return ZX_ERR_INVALID_ARGS;
193 }
194 mtx_lock(&ps->lock);
195 uint32_t events = ps->events;
196 ps->events = 0;
197 if (ps->active == NULL) {
198 events |= PTY_EVENT_HANGUP;
199 }
200 *((uint32_t*) out_buf) = events;
201 device_state_clr(pc->zxdev, PTY_SIGNAL_EVENT);
202 mtx_unlock(&ps->lock);
203 *out_actual = sizeof(uint32_t);
204 return ZX_OK;
205 }
206 default:
207 if (ps->ioctl != NULL) {
208 return ps->ioctl(ps, op, in_buf, in_len, out_buf, out_len, out_actual);
209 } else {
210 return ZX_ERR_NOT_SUPPORTED;
211 }
212 }
213 }
214
pty_client_release(void * ctx)215 static void pty_client_release(void* ctx) {
216 pty_client_t* pc = ctx;
217 pty_server_t* ps = pc->srv;
218
219 mtx_lock(&ps->lock);
220
221 // remove client from list of clients and downref server
222 list_delete(&pc->node);
223 pc->srv = NULL;
224 int refcount = --ps->refcount;
225
226 if (ps->control == pc) {
227 ps->control = NULL;
228 }
229 if (ps->active == pc) {
230 // signal controlling client as well, if there is one
231 if (ps->control) {
232 device_state_set(ps->control->zxdev, PTY_SIGNAL_EVENT | DEV_STATE_HANGUP);
233 }
234 ps->active = NULL;
235 }
236 // signal server, if the last client has gone away
237 if (list_is_empty(&ps->clients)) {
238 device_state_clr_set(ps->zxdev, DEV_STATE_WRITABLE, DEV_STATE_READABLE | DEV_STATE_HANGUP);
239 }
240 mtx_unlock(&ps->lock);
241
242 if (refcount == 0) {
243 xprintf("pty srv %p release (from client)\n", ps);
244 if (ps->release) {
245 ps->release(ps);
246 } else {
247 free(ps);
248 }
249 }
250
251 xprintf("pty cli %p (id=%u) release\n", pc, pc->id);
252 free(pc);
253 }
254
pty_client_openat(void * ctx,zx_device_t ** out,const char * path,uint32_t flags)255 zx_status_t pty_client_openat(void* ctx, zx_device_t** out, const char* path, uint32_t flags) {
256 pty_client_t* pc = ctx;
257 pty_server_t* ps = pc->srv;
258 uint32_t id = strtoul(path, NULL, 0);
259 // only controlling clients may create additional clients
260 if (!(pc->flags & PTY_CLI_CONTROL)) {
261 return ZX_ERR_ACCESS_DENIED;
262 }
263 // clients may not create controlling clients
264 if (id == 0) {
265 return ZX_ERR_INVALID_ARGS;
266 }
267 return pty_openat(ps, out, id, flags);
268 }
269
270 zx_protocol_device_t pc_ops = {
271 .version = DEVICE_OPS_VERSION,
272 // .open = default, allow cloning
273 .open_at = pty_client_openat,
274 .release = pty_client_release,
275 .read = pty_client_read,
276 .write = pty_client_write,
277 .ioctl = pty_client_ioctl,
278 };
279
280 // used by both client and server ptys to create new client ptys
281
pty_openat(pty_server_t * ps,zx_device_t ** out,uint32_t id,uint32_t flags)282 static zx_status_t pty_openat(pty_server_t* ps, zx_device_t** out, uint32_t id, uint32_t flags) {
283 pty_client_t* pc;
284 if ((pc = calloc(1, sizeof(pty_client_t))) == NULL) {
285 return ZX_ERR_NO_MEMORY;
286 }
287
288 pc->id = id;
289 pc->flags = 0;
290 pc->fifo.head = 0;
291 pc->fifo.tail = 0;
292 zx_status_t status;
293
294 unsigned num_clients = 0;
295 mtx_lock(&ps->lock);
296 // require that client ID is unique
297 pty_client_t* c;
298 list_for_every_entry(&ps->clients, c, pty_client_t, node) {
299 if (c->id == id) {
300 mtx_unlock(&ps->lock);
301 free(pc);
302 return ZX_ERR_INVALID_ARGS;
303 }
304 num_clients++;
305 }
306 list_add_tail(&ps->clients, &pc->node);
307
308 ps->refcount++;
309 mtx_unlock(&ps->lock);
310
311 pc->srv = ps;
312
313 device_add_args_t args = {
314 .version = DEVICE_ADD_ARGS_VERSION,
315 .name = "pty",
316 .ctx = pc,
317 .ops = &pc_ops,
318 .flags = DEVICE_ADD_INSTANCE,
319 };
320
321 status = device_add(ps->zxdev, &args, &pc->zxdev);
322 if (status < 0) {
323 pty_client_release(pc->zxdev);
324 return status;
325 }
326
327 if (ps->active == NULL) {
328 pty_make_active_locked(ps, pc);
329 }
330 if (id == 0) {
331 ps->control = pc;
332 pc->flags |= PTY_CLI_CONTROL;
333 }
334
335 xprintf("pty cli %p (id=%u) created (srv %p)\n", pc, pc->id, ps);
336
337 mtx_lock(&ps->lock);
338 if (num_clients == 0) {
339 // if there were no clients, make sure we take server
340 // out of HANGUP and READABLE, where it landed if all
341 // its clients had closed
342 device_state_clr(ps->zxdev, DEV_STATE_READABLE | DEV_STATE_HANGUP);
343 }
344 pty_adjust_signals_locked(pc);
345 mtx_unlock(&ps->lock);
346
347 *out = pc->zxdev;
348 return ZX_OK;
349 }
350
351
352 // pty server device operations
353
pty_server_resume_locked(pty_server_t * ps)354 void pty_server_resume_locked(pty_server_t* ps) {
355 if (ps->active) {
356 device_state_set(ps->active->zxdev, DEV_STATE_WRITABLE);
357 }
358 }
359
pty_server_send(pty_server_t * ps,const void * data,size_t len,bool atomic,size_t * actual)360 zx_status_t pty_server_send(pty_server_t* ps, const void* data, size_t len, bool atomic, size_t* actual) {
361 //TODO: rw signals
362 zx_status_t status;
363 mtx_lock(&ps->lock);
364 if (ps->active) {
365 pty_client_t* pc = ps->active;
366 bool was_empty = pty_fifo_is_empty(&pc->fifo);
367 if (atomic || (pc->flags & PTY_CLI_RAW_MODE)) {
368 *actual = pty_fifo_write(&pc->fifo, data, len, atomic);
369 } else {
370 if (len > PTY_FIFO_SIZE) {
371 len = PTY_FIFO_SIZE;
372 }
373 const uint8_t *ch = data;
374 unsigned n = 0;
375 unsigned evt = 0;
376 while (n < len) {
377 if (*ch++ == CTRL_C) {
378 evt = PTY_EVENT_INTERRUPT;
379 break;
380 }
381 n++;
382 }
383 size_t r = pty_fifo_write(&pc->fifo, data, n, false);
384 if ((r == n) && evt) {
385 // consume the event
386 r++;
387 ps->events |= evt;
388 xprintf("pty cli %p evt %x\n", pc, evt);
389 if (ps->control) {
390 device_state_set(ps->control->zxdev, PTY_SIGNAL_EVENT);
391 }
392 }
393 *actual = r;
394 }
395 if (was_empty && *actual) {
396 device_state_set(pc->zxdev, DEV_STATE_READABLE);
397 }
398 if (pty_fifo_is_full(&pc->fifo)) {
399 device_state_clr(ps->zxdev, DEV_STATE_WRITABLE);
400 }
401 status = ZX_OK;
402 } else {
403 *actual = 0;
404 status = ZX_ERR_PEER_CLOSED;
405 }
406 mtx_unlock(&ps->lock);
407 return status;
408 }
409
pty_server_set_window_size(pty_server_t * ps,uint32_t w,uint32_t h)410 void pty_server_set_window_size(pty_server_t* ps, uint32_t w, uint32_t h) {
411 mtx_lock(&ps->lock);
412 ps->width = w;
413 ps->height = h;
414 //TODO signal?
415 mtx_unlock(&ps->lock);
416 }
417
pty_server_openat(void * ctx,zx_device_t ** out,const char * path,uint32_t flags)418 zx_status_t pty_server_openat(void* ctx, zx_device_t** out, const char* path, uint32_t flags) {
419 pty_server_t* ps = ctx;
420 uint32_t id = strtoul(path, NULL, 0);
421 return pty_openat(ps, out, id, flags);
422 }
423
pty_server_release(void * ctx)424 void pty_server_release(void* ctx) {
425 pty_server_t* ps = ctx;
426
427 mtx_lock(&ps->lock);
428 // inform clients that server is gone
429 pty_client_t* pc;
430 list_for_every_entry(&ps->clients, pc, pty_client_t, node) {
431 pc->flags = (pc->flags & (~PTY_CLI_ACTIVE)) | PTY_CLI_PEER_CLOSED;
432 device_state_set(pc->zxdev, DEV_STATE_HANGUP);
433 }
434 int32_t refcount = --ps->refcount;
435 mtx_unlock(&ps->lock);
436
437 if (refcount == 0) {
438 xprintf("pty srv %p release (from server)\n", ps);
439 if (ps->release) {
440 ps->release(ps);
441 } else {
442 free(ps);
443 }
444 }
445 }
446
pty_server_init(pty_server_t * ps)447 void pty_server_init(pty_server_t* ps) {
448 mtx_init(&ps->lock, mtx_plain);
449 ps->refcount = 1;
450 list_initialize(&ps->clients);
451 ps->active = NULL;
452 ps->control = NULL;
453 ps->events = 0;
454 ps->width = 0;
455 ps->height = 0;
456 }
457