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