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
35struct 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
44static 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
50static 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
74static 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
101static 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
119static 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
136static 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
215static 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
255zx_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
270zx_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
282static 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
354void 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
360zx_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
410void 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
418zx_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
424void 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
447void 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