t* st + patches and config URI git clone git://git.codevoid.de/st-sdk DIR Log DIR Files DIR Refs DIR README DIR LICENSE --- tst.c (57387B) --- 1 /* See LICENSE for license details. */ 2 #include <ctype.h> 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <limits.h> 6 #include <pwd.h> 7 #include <stdarg.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <signal.h> 12 #include <sys/ioctl.h> 13 #include <sys/select.h> 14 #include <sys/types.h> 15 #include <sys/wait.h> 16 #include <termios.h> 17 #include <unistd.h> 18 #include <wchar.h> 19 20 #include "st.h" 21 #include "win.h" 22 23 #if defined(__linux) 24 #include <pty.h> 25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 26 #include <util.h> 27 #elif defined(__FreeBSD__) || defined(__DragonFly__) 28 #include <libutil.h> 29 #endif 30 31 /* Arbitrary sizes */ 32 #define UTF_INVALID 0xFFFD 33 #define UTF_SIZ 4 34 #define ESC_BUF_SIZ (128*UTF_SIZ) 35 #define ESC_ARG_SIZ 16 36 #define STR_BUF_SIZ ESC_BUF_SIZ 37 #define STR_ARG_SIZ ESC_ARG_SIZ 38 #define HISTSIZE 2000 39 40 /* macros */ 41 #define IS_SET(flag) ((term.mode & (flag)) != 0) 42 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) 43 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 44 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 45 #define ISDELIM(u) (u && wcschr(worddelimiters, u)) 46 #define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ 47 term.scr + HISTSIZE + 1) % HISTSIZE] : \ 48 term.line[(y) - term.scr]) 49 50 enum term_mode { 51 MODE_WRAP = 1 << 0, 52 MODE_INSERT = 1 << 1, 53 MODE_ALTSCREEN = 1 << 2, 54 MODE_CRLF = 1 << 3, 55 MODE_ECHO = 1 << 4, 56 MODE_PRINT = 1 << 5, 57 MODE_UTF8 = 1 << 6, 58 }; 59 60 enum cursor_movement { 61 CURSOR_SAVE, 62 CURSOR_LOAD 63 }; 64 65 enum cursor_state { 66 CURSOR_DEFAULT = 0, 67 CURSOR_WRAPNEXT = 1, 68 CURSOR_ORIGIN = 2 69 }; 70 71 enum charset { 72 CS_GRAPHIC0, 73 CS_GRAPHIC1, 74 CS_UK, 75 CS_USA, 76 CS_MULTI, 77 CS_GER, 78 CS_FIN 79 }; 80 81 enum escape_state { 82 ESC_START = 1, 83 ESC_CSI = 2, 84 ESC_STR = 4, /* DCS, OSC, PM, APC */ 85 ESC_ALTCHARSET = 8, 86 ESC_STR_END = 16, /* a final string was encountered */ 87 ESC_TEST = 32, /* Enter in test mode */ 88 ESC_UTF8 = 64, 89 }; 90 91 typedef struct { 92 Glyph attr; /* current char attributes */ 93 int x; 94 int y; 95 char state; 96 } TCursor; 97 98 typedef struct { 99 int mode; 100 int type; 101 int snap; 102 /* 103 * Selection variables: 104 * nb – normalized coordinates of the beginning of the selection 105 * ne – normalized coordinates of the end of the selection 106 * ob – original coordinates of the beginning of the selection 107 * oe – original coordinates of the end of the selection 108 */ 109 struct { 110 int x, y; 111 } nb, ne, ob, oe; 112 113 int alt; 114 } Selection; 115 116 /* Internal representation of the screen */ 117 typedef struct { 118 int row; /* nb row */ 119 int col; /* nb col */ 120 Line *line; /* screen */ 121 Line *alt; /* alternate screen */ 122 Line hist[HISTSIZE]; /* history buffer */ 123 int histi; /* history index */ 124 int scr; /* scroll back */ 125 int *dirty; /* dirtyness of lines */ 126 TCursor c; /* cursor */ 127 int ocx; /* old cursor col */ 128 int ocy; /* old cursor row */ 129 int top; /* top scroll limit */ 130 int bot; /* bottom scroll limit */ 131 int mode; /* terminal mode flags */ 132 int esc; /* escape state flags */ 133 char trantbl[4]; /* charset table translation */ 134 int charset; /* current charset */ 135 int icharset; /* selected charset for sequence */ 136 int *tabs; 137 Rune lastc; /* last printed char outside of sequence, 0 if control */ 138 } Term; 139 140 /* CSI Escape sequence structs */ 141 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ 142 typedef struct { 143 char buf[ESC_BUF_SIZ]; /* raw string */ 144 size_t len; /* raw string length */ 145 char priv; 146 int arg[ESC_ARG_SIZ]; 147 int narg; /* nb of args */ 148 char mode[2]; 149 } CSIEscape; 150 151 /* STR Escape sequence structs */ 152 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */ 153 typedef struct { 154 char type; /* ESC type ... */ 155 char *buf; /* allocated raw string */ 156 size_t siz; /* allocation size */ 157 size_t len; /* raw string length */ 158 char *args[STR_ARG_SIZ]; 159 int narg; /* nb of args */ 160 } STREscape; 161 162 static void execsh(char *, char **); 163 static void stty(char **); 164 static void sigchld(int); 165 static void ttywriteraw(const char *, size_t); 166 167 static void csidump(void); 168 static void csihandle(void); 169 static void csiparse(void); 170 static void csireset(void); 171 static int eschandle(uchar); 172 static void strdump(void); 173 static void strhandle(void); 174 static void strparse(void); 175 static void strreset(void); 176 177 static void tprinter(char *, size_t); 178 static void tdumpsel(void); 179 static void tdumpline(int); 180 static void tdump(void); 181 static void tclearregion(int, int, int, int); 182 static void tcursor(int); 183 static void tdeletechar(int); 184 static void tdeleteline(int); 185 static void tinsertblank(int); 186 static void tinsertblankline(int); 187 static int tlinelen(int); 188 static void tmoveto(int, int); 189 static void tmoveato(int, int); 190 static void tnewline(int); 191 static void tputtab(int); 192 static void tputc(Rune); 193 static void treset(void); 194 static void tscrollup(int, int, int); 195 static void tscrolldown(int, int, int); 196 static void tsetattr(int *, int); 197 static void tsetchar(Rune, Glyph *, int, int); 198 static void tsetdirt(int, int); 199 static void tsetscroll(int, int); 200 static void tswapscreen(void); 201 static void tsetmode(int, int, int *, int); 202 static int twrite(const char *, int, int); 203 static void tfulldirt(void); 204 static void tcontrolcode(uchar ); 205 static void tdectest(char ); 206 static void tdefutf8(char); 207 static int32_t tdefcolor(int *, int *, int); 208 static void tdeftran(char); 209 static void tstrsequence(uchar); 210 211 static void drawregion(int, int, int, int); 212 213 static void selnormalize(void); 214 static void selscroll(int, int); 215 static void selsnap(int *, int *, int); 216 217 static size_t utf8decode(const char *, Rune *, size_t); 218 static Rune utf8decodebyte(char, size_t *); 219 static char utf8encodebyte(Rune, size_t); 220 static size_t utf8validate(Rune *, size_t); 221 222 static char *base64dec(const char *); 223 static char base64dec_getc(const char **); 224 225 static ssize_t xwrite(int, const char *, size_t); 226 227 /* Globals */ 228 static Term term; 229 static Selection sel; 230 static CSIEscape csiescseq; 231 static STREscape strescseq; 232 static int iofd = 1; 233 static int cmdfd; 234 static pid_t pid; 235 236 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 237 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 238 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 239 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 240 241 ssize_t 242 xwrite(int fd, const char *s, size_t len) 243 { 244 size_t aux = len; 245 ssize_t r; 246 247 while (len > 0) { 248 r = write(fd, s, len); 249 if (r < 0) 250 return r; 251 len -= r; 252 s += r; 253 } 254 255 return aux; 256 } 257 258 void * 259 xmalloc(size_t len) 260 { 261 void *p; 262 263 if (!(p = malloc(len))) 264 die("malloc: %s\n", strerror(errno)); 265 266 return p; 267 } 268 269 void * 270 xrealloc(void *p, size_t len) 271 { 272 if ((p = realloc(p, len)) == NULL) 273 die("realloc: %s\n", strerror(errno)); 274 275 return p; 276 } 277 278 char * 279 xstrdup(char *s) 280 { 281 if ((s = strdup(s)) == NULL) 282 die("strdup: %s\n", strerror(errno)); 283 284 return s; 285 } 286 287 size_t 288 utf8decode(const char *c, Rune *u, size_t clen) 289 { 290 size_t i, j, len, type; 291 Rune udecoded; 292 293 *u = UTF_INVALID; 294 if (!clen) 295 return 0; 296 udecoded = utf8decodebyte(c[0], &len); 297 if (!BETWEEN(len, 1, UTF_SIZ)) 298 return 1; 299 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 300 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 301 if (type != 0) 302 return j; 303 } 304 if (j < len) 305 return 0; 306 *u = udecoded; 307 utf8validate(u, len); 308 309 return len; 310 } 311 312 Rune 313 utf8decodebyte(char c, size_t *i) 314 { 315 for (*i = 0; *i < LEN(utfmask); ++(*i)) 316 if (((uchar)c & utfmask[*i]) == utfbyte[*i]) 317 return (uchar)c & ~utfmask[*i]; 318 319 return 0; 320 } 321 322 size_t 323 utf8encode(Rune u, char *c) 324 { 325 size_t len, i; 326 327 len = utf8validate(&u, 0); 328 if (len > UTF_SIZ) 329 return 0; 330 331 for (i = len - 1; i != 0; --i) { 332 c[i] = utf8encodebyte(u, 0); 333 u >>= 6; 334 } 335 c[0] = utf8encodebyte(u, len); 336 337 return len; 338 } 339 340 char 341 utf8encodebyte(Rune u, size_t i) 342 { 343 return utfbyte[i] | (u & ~utfmask[i]); 344 } 345 346 size_t 347 utf8validate(Rune *u, size_t i) 348 { 349 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 350 *u = UTF_INVALID; 351 for (i = 1; *u > utfmax[i]; ++i) 352 ; 353 354 return i; 355 } 356 357 static const char base64_digits[] = { 358 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 359 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 360 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 361 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 362 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 363 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 364 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 365 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 366 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 367 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 368 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 369 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 370 }; 371 372 char 373 base64dec_getc(const char **src) 374 { 375 while (**src && !isprint(**src)) 376 (*src)++; 377 return **src ? *((*src)++) : '='; /* emulate padding if string ends */ 378 } 379 380 char * 381 base64dec(const char *src) 382 { 383 size_t in_len = strlen(src); 384 char *result, *dst; 385 386 if (in_len % 4) 387 in_len += 4 - (in_len % 4); 388 result = dst = xmalloc(in_len / 4 * 3 + 1); 389 while (*src) { 390 int a = base64_digits[(unsigned char) base64dec_getc(&src)]; 391 int b = base64_digits[(unsigned char) base64dec_getc(&src)]; 392 int c = base64_digits[(unsigned char) base64dec_getc(&src)]; 393 int d = base64_digits[(unsigned char) base64dec_getc(&src)]; 394 395 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ 396 if (a == -1 || b == -1) 397 break; 398 399 *dst++ = (a << 2) | ((b & 0x30) >> 4); 400 if (c == -1) 401 break; 402 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); 403 if (d == -1) 404 break; 405 *dst++ = ((c & 0x03) << 6) | d; 406 } 407 *dst = '\0'; 408 return result; 409 } 410 411 void 412 selinit(void) 413 { 414 sel.mode = SEL_IDLE; 415 sel.snap = 0; 416 sel.ob.x = -1; 417 } 418 419 int 420 tlinelen(int y) 421 { 422 int i = term.col; 423 424 if (TLINE(y)[i - 1].mode & ATTR_WRAP) 425 return i; 426 427 while (i > 0 && TLINE(y)[i - 1].u == ' ') 428 --i; 429 430 return i; 431 } 432 433 void 434 selstart(int col, int row, int snap) 435 { 436 selclear(); 437 sel.mode = SEL_EMPTY; 438 sel.type = SEL_REGULAR; 439 sel.alt = IS_SET(MODE_ALTSCREEN); 440 sel.snap = snap; 441 sel.oe.x = sel.ob.x = col; 442 sel.oe.y = sel.ob.y = row; 443 selnormalize(); 444 445 if (sel.snap != 0) 446 sel.mode = SEL_READY; 447 tsetdirt(sel.nb.y, sel.ne.y); 448 } 449 450 void 451 selextend(int col, int row, int type, int done) 452 { 453 int oldey, oldex, oldsby, oldsey, oldtype; 454 455 if (sel.mode == SEL_IDLE) 456 return; 457 if (done && sel.mode == SEL_EMPTY) { 458 selclear(); 459 return; 460 } 461 462 oldey = sel.oe.y; 463 oldex = sel.oe.x; 464 oldsby = sel.nb.y; 465 oldsey = sel.ne.y; 466 oldtype = sel.type; 467 468 sel.oe.x = col; 469 sel.oe.y = row; 470 selnormalize(); 471 sel.type = type; 472 473 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) 474 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 475 476 sel.mode = done ? SEL_IDLE : SEL_READY; 477 } 478 479 void 480 selnormalize(void) 481 { 482 int i; 483 484 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 485 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 486 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 487 } else { 488 sel.nb.x = MIN(sel.ob.x, sel.oe.x); 489 sel.ne.x = MAX(sel.ob.x, sel.oe.x); 490 } 491 sel.nb.y = MIN(sel.ob.y, sel.oe.y); 492 sel.ne.y = MAX(sel.ob.y, sel.oe.y); 493 494 selsnap(&sel.nb.x, &sel.nb.y, -1); 495 selsnap(&sel.ne.x, &sel.ne.y, +1); 496 497 /* expand selection over line breaks */ 498 if (sel.type == SEL_RECTANGULAR) 499 return; 500 i = tlinelen(sel.nb.y); 501 if (i < sel.nb.x) 502 sel.nb.x = i; 503 if (tlinelen(sel.ne.y) <= sel.ne.x) 504 sel.ne.x = term.col - 1; 505 } 506 507 int 508 selected(int x, int y) 509 { 510 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || 511 sel.alt != IS_SET(MODE_ALTSCREEN)) 512 return 0; 513 514 if (sel.type == SEL_RECTANGULAR) 515 return BETWEEN(y, sel.nb.y, sel.ne.y) 516 && BETWEEN(x, sel.nb.x, sel.ne.x); 517 518 return BETWEEN(y, sel.nb.y, sel.ne.y) 519 && (y != sel.nb.y || x >= sel.nb.x) 520 && (y != sel.ne.y || x <= sel.ne.x); 521 } 522 523 void 524 selsnap(int *x, int *y, int direction) 525 { 526 int newx, newy, xt, yt; 527 int delim, prevdelim; 528 Glyph *gp, *prevgp; 529 530 switch (sel.snap) { 531 case SNAP_WORD: 532 /* 533 * Snap around if the word wraps around at the end or 534 * beginning of a line. 535 */ 536 prevgp = &TLINE(*y)[*x]; 537 prevdelim = ISDELIM(prevgp->u); 538 for (;;) { 539 newx = *x + direction; 540 newy = *y; 541 if (!BETWEEN(newx, 0, term.col - 1)) { 542 newy += direction; 543 newx = (newx + term.col) % term.col; 544 if (!BETWEEN(newy, 0, term.row - 1)) 545 break; 546 547 if (direction > 0) 548 yt = *y, xt = *x; 549 else 550 yt = newy, xt = newx; 551 if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) 552 break; 553 } 554 555 if (newx >= tlinelen(newy)) 556 break; 557 558 gp = &TLINE(newy)[newx]; 559 delim = ISDELIM(gp->u); 560 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 561 || (delim && gp->u != prevgp->u))) 562 break; 563 564 *x = newx; 565 *y = newy; 566 prevgp = gp; 567 prevdelim = delim; 568 } 569 break; 570 case SNAP_LINE: 571 /* 572 * Snap around if the the previous line or the current one 573 * has set ATTR_WRAP at its end. Then the whole next or 574 * previous line will be selected. 575 */ 576 *x = (direction < 0) ? 0 : term.col - 1; 577 if (direction < 0) { 578 for (; *y > 0; *y += direction) { 579 if (!(TLINE(*y-1)[term.col-1].mode 580 & ATTR_WRAP)) { 581 break; 582 } 583 } 584 } else if (direction > 0) { 585 for (; *y < term.row-1; *y += direction) { 586 if (!(TLINE(*y)[term.col-1].mode 587 & ATTR_WRAP)) { 588 break; 589 } 590 } 591 } 592 break; 593 } 594 } 595 596 char * 597 getsel(void) 598 { 599 char *str, *ptr; 600 int y, bufsize, lastx, linelen; 601 Glyph *gp, *last; 602 603 if (sel.ob.x == -1) 604 return NULL; 605 606 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 607 ptr = str = xmalloc(bufsize); 608 609 /* append every set & selected glyph to the selection */ 610 for (y = sel.nb.y; y <= sel.ne.y; y++) { 611 if ((linelen = tlinelen(y)) == 0) { 612 *ptr++ = '\n'; 613 continue; 614 } 615 616 if (sel.type == SEL_RECTANGULAR) { 617 gp = &TLINE(y)[sel.nb.x]; 618 lastx = sel.ne.x; 619 } else { 620 gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; 621 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 622 } 623 last = &TLINE(y)[MIN(lastx, linelen-1)]; 624 while (last >= gp && last->u == ' ') 625 --last; 626 627 for ( ; gp <= last; ++gp) { 628 if (gp->mode & ATTR_WDUMMY) 629 continue; 630 631 ptr += utf8encode(gp->u, ptr); 632 } 633 634 /* 635 * Copy and pasting of line endings is inconsistent 636 * in the inconsistent terminal and GUI world. 637 * The best solution seems like to produce '\n' when 638 * something is copied from st and convert '\n' to 639 * '\r', when something to be pasted is received by 640 * st. 641 * FIXME: Fix the computer world. 642 */ 643 if ((y < sel.ne.y || lastx >= linelen) && 644 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) 645 *ptr++ = '\n'; 646 } 647 *ptr = 0; 648 return str; 649 } 650 651 void 652 selclear(void) 653 { 654 if (sel.ob.x == -1) 655 return; 656 sel.mode = SEL_IDLE; 657 sel.ob.x = -1; 658 tsetdirt(sel.nb.y, sel.ne.y); 659 } 660 661 void 662 die(const char *errstr, ...) 663 { 664 va_list ap; 665 666 va_start(ap, errstr); 667 vfprintf(stderr, errstr, ap); 668 va_end(ap); 669 exit(1); 670 } 671 672 void 673 execsh(char *cmd, char **args) 674 { 675 char *sh, *prog, *arg; 676 const struct passwd *pw; 677 678 errno = 0; 679 if ((pw = getpwuid(getuid())) == NULL) { 680 if (errno) 681 die("getpwuid: %s\n", strerror(errno)); 682 else 683 die("who are you?\n"); 684 } 685 686 if ((sh = getenv("SHELL")) == NULL) 687 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; 688 689 if (args) { 690 prog = args[0]; 691 arg = NULL; 692 } else if (scroll) { 693 prog = scroll; 694 arg = utmp ? utmp : sh; 695 } else if (utmp) { 696 prog = utmp; 697 arg = NULL; 698 } else { 699 prog = sh; 700 arg = NULL; 701 } 702 DEFAULT(args, ((char *[]) {prog, arg, NULL})); 703 704 unsetenv("COLUMNS"); 705 unsetenv("LINES"); 706 unsetenv("TERMCAP"); 707 setenv("LOGNAME", pw->pw_name, 1); 708 setenv("USER", pw->pw_name, 1); 709 setenv("SHELL", sh, 1); 710 setenv("HOME", pw->pw_dir, 1); 711 setenv("TERM", termname, 1); 712 713 signal(SIGCHLD, SIG_DFL); 714 signal(SIGHUP, SIG_DFL); 715 signal(SIGINT, SIG_DFL); 716 signal(SIGQUIT, SIG_DFL); 717 signal(SIGTERM, SIG_DFL); 718 signal(SIGALRM, SIG_DFL); 719 720 execvp(prog, args); 721 _exit(1); 722 } 723 724 void 725 sigchld(int a) 726 { 727 int stat; 728 pid_t p; 729 730 if ((p = waitpid(pid, &stat, WNOHANG)) < 0) 731 die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); 732 733 if (pid != p) 734 return; 735 736 if (WIFEXITED(stat) && WEXITSTATUS(stat)) 737 die("child exited with status %d\n", WEXITSTATUS(stat)); 738 else if (WIFSIGNALED(stat)) 739 die("child terminated due to signal %d\n", WTERMSIG(stat)); 740 _exit(0); 741 } 742 743 void 744 stty(char **args) 745 { 746 char cmd[_POSIX_ARG_MAX], **p, *q, *s; 747 size_t n, siz; 748 749 if ((n = strlen(stty_args)) > sizeof(cmd)-1) 750 die("incorrect stty parameters\n"); 751 memcpy(cmd, stty_args, n); 752 q = cmd + n; 753 siz = sizeof(cmd) - n; 754 for (p = args; p && (s = *p); ++p) { 755 if ((n = strlen(s)) > siz-1) 756 die("stty parameter length too long\n"); 757 *q++ = ' '; 758 memcpy(q, s, n); 759 q += n; 760 siz -= n + 1; 761 } 762 *q = '\0'; 763 if (system(cmd) != 0) 764 perror("Couldn't call stty"); 765 } 766 767 int 768 ttynew(char *line, char *cmd, char *out, char **args) 769 { 770 int m, s; 771 772 if (out) { 773 term.mode |= MODE_PRINT; 774 iofd = (!strcmp(out, "-")) ? 775 1 : open(out, O_WRONLY | O_CREAT, 0666); 776 if (iofd < 0) { 777 fprintf(stderr, "Error opening %s:%s\n", 778 out, strerror(errno)); 779 } 780 } 781 782 if (line) { 783 if ((cmdfd = open(line, O_RDWR)) < 0) 784 die("open line '%s' failed: %s\n", 785 line, strerror(errno)); 786 dup2(cmdfd, 0); 787 stty(args); 788 return cmdfd; 789 } 790 791 /* seems to work fine on linux, openbsd and freebsd */ 792 if (openpty(&m, &s, NULL, NULL, NULL) < 0) 793 die("openpty failed: %s\n", strerror(errno)); 794 795 switch (pid = fork()) { 796 case -1: 797 die("fork failed: %s\n", strerror(errno)); 798 break; 799 case 0: 800 close(iofd); 801 setsid(); /* create a new process group */ 802 dup2(s, 0); 803 dup2(s, 1); 804 dup2(s, 2); 805 if (ioctl(s, TIOCSCTTY, NULL) < 0) 806 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); 807 close(s); 808 close(m); 809 #ifdef __OpenBSD__ 810 if (pledge("stdio getpw proc exec", NULL) == -1) 811 die("pledge\n"); 812 #endif 813 execsh(cmd, args); 814 break; 815 default: 816 #ifdef __OpenBSD__ 817 if (pledge("stdio rpath tty proc", NULL) == -1) 818 die("pledge\n"); 819 #endif 820 close(s); 821 cmdfd = m; 822 signal(SIGCHLD, sigchld); 823 break; 824 } 825 return cmdfd; 826 } 827 828 size_t 829 ttyread(void) 830 { 831 static char buf[BUFSIZ]; 832 static int buflen = 0; 833 int ret, written; 834 835 /* append read bytes to unprocessed bytes */ 836 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); 837 838 switch (ret) { 839 case 0: 840 exit(0); 841 case -1: 842 die("couldn't read from shell: %s\n", strerror(errno)); 843 default: 844 buflen += ret; 845 written = twrite(buf, buflen, 0); 846 buflen -= written; 847 /* keep any incomplete UTF-8 byte sequence for the next call */ 848 if (buflen > 0) 849 memmove(buf, buf + written, buflen); 850 return ret; 851 } 852 } 853 854 void 855 ttywrite(const char *s, size_t n, int may_echo) 856 { 857 const char *next; 858 Arg arg = (Arg) { .i = term.scr }; 859 860 kscrolldown(&arg); 861 862 if (may_echo && IS_SET(MODE_ECHO)) 863 twrite(s, n, 1); 864 865 if (!IS_SET(MODE_CRLF)) { 866 ttywriteraw(s, n); 867 return; 868 } 869 870 /* This is similar to how the kernel handles ONLCR for ttys */ 871 while (n > 0) { 872 if (*s == '\r') { 873 next = s + 1; 874 ttywriteraw("\r\n", 2); 875 } else { 876 next = memchr(s, '\r', n); 877 DEFAULT(next, s + n); 878 ttywriteraw(s, next - s); 879 } 880 n -= next - s; 881 s = next; 882 } 883 } 884 885 void 886 ttywriteraw(const char *s, size_t n) 887 { 888 fd_set wfd, rfd; 889 ssize_t r; 890 size_t lim = 256; 891 892 /* 893 * Remember that we are using a pty, which might be a modem line. 894 * Writing too much will clog the line. That's why we are doing this 895 * dance. 896 * FIXME: Migrate the world to Plan 9. 897 */ 898 while (n > 0) { 899 FD_ZERO(&wfd); 900 FD_ZERO(&rfd); 901 FD_SET(cmdfd, &wfd); 902 FD_SET(cmdfd, &rfd); 903 904 /* Check if we can write. */ 905 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { 906 if (errno == EINTR) 907 continue; 908