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