1 /* 2 * Copyright (C) 1984-2025 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information, see the README file. 8 */ 9 10 11 /* 12 * Operating system dependent routines. 13 * 14 * Most of the stuff in here is based on Unix, but an attempt 15 * has been made to make things work on other operating systems. 16 * This will sometimes result in a loss of functionality, unless 17 * someone rewrites code specifically for the new operating system. 18 * 19 * The makefile provides defines to decide whether various 20 * Unix features are present. 21 */ 22 23 #include "less.h" 24 #include <signal.h> 25 #include <setjmp.h> 26 #if MSDOS_COMPILER==WIN32C 27 #include <windows.h> 28 #endif 29 #if HAVE_TIME_H 30 #include <time.h> 31 #endif 32 #if HAVE_ERRNO_H 33 #include <errno.h> 34 #endif 35 #if MUST_DEFINE_ERRNO 36 extern int errno; 37 #endif 38 #if HAVE_VALUES_H 39 #include <values.h> 40 #endif 41 42 #if defined(__APPLE__) 43 #include <sys/utsname.h> 44 #endif 45 46 #if HAVE_POLL && !MSDOS_COMPILER && !defined(__MVS__) 47 #define USE_POLL 1 48 static lbool use_poll = TRUE; 49 #else 50 #define USE_POLL 0 51 #endif 52 #if USE_POLL 53 #include <poll.h> 54 static lbool any_data = FALSE; 55 #endif 56 57 /* 58 * BSD setjmp() saves (and longjmp() restores) the signal mask. 59 * This costs a system call or two per setjmp(), so if possible we clear the 60 * signal mask with sigsetmask(), and use _setjmp()/_longjmp() instead. 61 * On other systems, setjmp() doesn't affect the signal mask and so 62 * _setjmp() does not exist; we just use setjmp(). 63 */ 64 #if HAVE_SIGSETJMP 65 #define SET_JUMP(label) sigsetjmp(label, 1) 66 #define LONG_JUMP(label, val) siglongjmp(label, val) 67 #define JUMP_BUF sigjmp_buf 68 #else 69 #if HAVE__SETJMP && HAVE_SIGSETMASK 70 #define SET_JUMP(label) _setjmp(label) 71 #define LONG_JUMP(label, val) _longjmp(label, val) 72 #define JUMP_BUF jmp_buf 73 #else 74 #define SET_JUMP(label) setjmp(label) 75 #define LONG_JUMP(label, val) longjmp(label, val) 76 #define JUMP_BUF jmp_buf 77 #endif 78 #endif 79 80 static lbool reading; 81 static lbool opening; 82 public lbool waiting_for_data; 83 public int consecutive_nulls = 0; 84 public lbool getting_one_screen = FALSE; 85 public lbool no_poll = FALSE; 86 87 /* Milliseconds to wait for data before displaying "waiting for data" message. */ 88 static int waiting_for_data_delay = 4000; 89 /* Max milliseconds expected to "normally" read and display a screen of text. */ 90 public int screenfill_ms = 3000; 91 92 static JUMP_BUF read_label; 93 static JUMP_BUF open_label; 94 95 extern int sigs; 96 extern int ignore_eoi; 97 extern int exit_F_on_close; 98 extern int follow_mode; 99 extern int scanning_eof; 100 extern char intr_char; 101 extern int is_tty; 102 extern int quit_if_one_screen; 103 extern int one_screen; 104 #if HAVE_TIME 105 extern time_type less_start_time; 106 #endif 107 #if !MSDOS_COMPILER 108 extern int tty; 109 #endif 110 111 public void init_poll(void) 112 { 113 constant char *delay = lgetenv("LESS_DATA_DELAY"); 114 int idelay = (delay == NULL) ? 0 : atoi(delay); 115 if (idelay > 0) 116 waiting_for_data_delay = idelay; 117 delay = lgetenv("LESS_SCREENFILL_TIME"); 118 idelay = (delay == NULL) ? 0 : atoi(delay); 119 if (idelay > 0) 120 screenfill_ms = idelay; 121 #if USE_POLL 122 #if defined(__APPLE__) 123 /* In old versions of MacOS, poll() does not work with /dev/tty. */ 124 struct utsname uts; 125 if (uname(&uts) < 0 || lstrtoi(uts.release, NULL, 10) < 20) 126 use_poll = FALSE; 127 #endif 128 #endif 129 } 130 131 #if USE_POLL 132 /* 133 * Check whether data is available, either from a file/pipe or from the tty. 134 * Return READ_AGAIN if no data currently available, but caller should retry later. 135 * Return READ_INTR to abort F command (forw_loop). 136 * Return 0 if safe to read from fd. 137 */ 138 static int check_poll(int fd, int tty) 139 { 140 struct pollfd poller[2] = { { fd, POLLIN, 0 }, { tty, POLLIN, 0 } }; 141 int timeout = (waiting_for_data && !(scanning_eof && follow_mode == FOLLOW_NAME)) ? -1 : (ignore_eoi && !waiting_for_data) ? 0 : waiting_for_data_delay; 142 #if HAVE_TIME 143 if (getting_one_screen && get_time() < less_start_time + screenfill_ms/1000) 144 return (0); 145 #endif 146 if (!any_data) 147 { 148 /* 149 * Don't do polling if no data has yet been received, 150 * to allow a program piping data into less to have temporary 151 * access to the tty (like sudo asking for a password). 152 */ 153 return (0); 154 } 155 poll(poller, 2, timeout); 156 #if LESSTEST 157 if (!is_lesstest()) /* Check for ^X only on a real tty. */ 158 #endif /*LESSTEST*/ 159 { 160 if (poller[1].revents & POLLIN) 161 { 162 int ch = getchr(); 163 if (ch < 0 || ch == intr_char) 164 /* Break out of "waiting for data". */ 165 return (READ_INTR); 166 ungetcc_back((char) ch); 167 if (!no_poll) 168 return (READ_INTR); 169 } 170 } 171 if (ignore_eoi && exit_F_on_close && (poller[0].revents & (POLLHUP|POLLIN)) == POLLHUP) 172 /* Break out of F loop on HUP due to --exit-follow-on-close. */ 173 return (READ_INTR); 174 if ((poller[0].revents & (POLLIN|POLLHUP|POLLERR)) == 0) 175 /* No data available; let caller take action, then try again. */ 176 return (READ_AGAIN); 177 /* There is data (or HUP/ERR) available. Safe to call read() without blocking. */ 178 return (0); 179 } 180 #endif /* USE_POLL */ 181 182 /* 183 * Is a character available to be read from the tty? 184 */ 185 public lbool ttyin_ready(void) 186 { 187 #if MSDOS_COMPILER==WIN32C 188 return win32_kbhit(); 189 #else 190 #if MSDOS_COMPILER 191 return kbhit(); 192 #else 193 #if USE_POLL 194 #if LESSTEST 195 if (is_lesstest()) 196 return FALSE; 197 #endif /*LESSTEST*/ 198 if (!use_poll) 199 return FALSE; 200 { 201 struct pollfd poller[1] = { { tty, POLLIN, 0 } }; 202 poll(poller, 1, 0); 203 return ((poller[0].revents & POLLIN) != 0); 204 } 205 #else 206 return FALSE; 207 #endif 208 #endif 209 #endif 210 } 211 212 public int supports_ctrl_x(void) 213 { 214 #if MSDOS_COMPILER==WIN32C 215 return (TRUE); 216 #else 217 #if USE_POLL 218 return (use_poll); 219 #else 220 return (FALSE); 221 #endif /* USE_POLL */ 222 #endif /* MSDOS_COMPILER==WIN32C */ 223 } 224 225 /* 226 * Like read() system call, but is deliberately interruptible. 227 * A call to intio() from a signal handler will interrupt 228 * any pending iread(). 229 */ 230 public ssize_t iread(int fd, unsigned char *buf, size_t len) 231 { 232 ssize_t n; 233 234 start: 235 #if MSDOS_COMPILER==WIN32C 236 if (ABORT_SIGS()) 237 return (READ_INTR); 238 #else 239 #if MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC 240 if (kbhit()) 241 { 242 int c; 243 244 c = getch(); 245 if (c == '\003') 246 return (READ_INTR); 247 ungetch(c); 248 } 249 #endif 250 #endif 251 if (!reading && SET_JUMP(read_label)) 252 { 253 /* 254 * We jumped here from intio. 255 */ 256 reading = FALSE; 257 #if HAVE_SIGPROCMASK 258 { 259 sigset_t mask; 260 sigemptyset(&mask); 261 sigprocmask(SIG_SETMASK, &mask, NULL); 262 } 263 #else 264 #if HAVE_SIGSETMASK 265 sigsetmask(0); 266 #else 267 #ifdef _OSK 268 sigmask(~0); 269 #endif 270 #endif 271 #endif 272 #if !MSDOS_COMPILER 273 if (fd != tty && !ABORT_SIGS()) 274 /* Non-interrupt signal like SIGWINCH. */ 275 return (READ_AGAIN); 276 #endif 277 return (READ_INTR); 278 } 279 280 flush(); 281 reading = TRUE; 282 #if MSDOS_COMPILER==DJGPPC 283 if (isatty(fd)) 284 { 285 /* 286 * Don't try reading from a TTY until a character is 287 * available, because that makes some background programs 288 * believe DOS is busy in a way that prevents those 289 * programs from working while "less" waits. 290 * {{ This code was added 12 Jan 2007; still needed? }} 291 */ 292 fd_set readfds; 293 294 FD_ZERO(&readfds); 295 FD_SET(fd, &readfds); 296 if (select(fd+1, &readfds, 0, 0, 0) == -1) 297 { 298 reading = FALSE; 299 return (READ_ERR); 300 } 301 } 302 #endif 303 #if USE_POLL 304 if (is_tty && fd != tty && use_poll && !(quit_if_one_screen && one_screen)) 305 { 306 int ret = check_poll(fd, tty); 307 if (ret != 0) 308 { 309 if (ret == READ_INTR) 310 sigs |= S_SWINTERRUPT; 311 reading = FALSE; 312 return (ret); 313 } 314 } 315 #else 316 #if MSDOS_COMPILER==WIN32C 317 if (!(quit_if_one_screen && one_screen) && win32_kbhit2(TRUE)) 318 { 319 int c; 320 lbool intr; 321 322 c = WIN32getch(); 323 intr = (c == CONTROL('C') || c == intr_char); 324 if (!intr) 325 WIN32ungetch((char) c); 326 if (intr || !no_poll) 327 { 328 sigs |= S_SWINTERRUPT; 329 reading = FALSE; 330 return (READ_INTR); 331 } 332 } 333 #endif 334 #endif 335 n = read(fd, buf, len); 336 reading = FALSE; 337 #if 0 338 /* 339 * This is a kludge to workaround a problem on some systems 340 * where terminating a remote tty connection causes read() to 341 * start returning 0 forever, instead of -1. 342 */ 343 { 344 if (!ignore_eoi) 345 { 346 if (n == 0) 347 consecutive_nulls++; 348 else 349 consecutive_nulls = 0; 350 if (consecutive_nulls > 20) 351 quit(QUIT_ERROR); 352 } 353 } 354 #endif 355 if (n < 0) 356 { 357 #if HAVE_ERRNO 358 /* 359 * Certain values of errno indicate we should just retry the read. 360 */ 361 #ifdef EINTR 362 if (errno == EINTR) 363 goto start; 364 #endif 365 #ifdef EAGAIN 366 if (errno == EAGAIN) 367 goto start; 368 #endif 369 #endif 370 return (READ_ERR); 371 } 372 #if USE_POLL 373 if (fd != tty && n > 0) 374 any_data = TRUE; 375 #endif 376 return (n); 377 } 378 379 /* 380 * Like open() system call, but is interruptible. 381 */ 382 public int iopen(constant char *filename, int flags) 383 { 384 int r; 385 while (!opening && SET_JUMP(open_label)) 386 { 387 opening = FALSE; 388 if (sigs & (S_INTERRUPT|S_SWINTERRUPT)) 389 { 390 sigs = 0; 391 #if HAVE_SETTABLE_ERRNO 392 #ifdef EINTR 393 errno = EINTR; 394 #endif 395 #endif 396 return -1; 397 } 398 psignals(); /* Handle S_STOP or S_WINCH */ 399 } 400 opening = TRUE; 401 r = open(filename, flags); 402 opening = FALSE; 403 return r; 404 } 405 406 /* 407 * Interrupt a pending iopen() or iread(). 408 */ 409 public void intio(void) 410 { 411 if (opening) 412 { 413 LONG_JUMP(open_label, 1); 414 } 415 if (reading) 416 { 417 LONG_JUMP(read_label, 1); 418 } 419 } 420 421 /* 422 * Return the current time. 423 */ 424 #if HAVE_TIME 425 public time_type get_time(void) 426 { 427 time_type t; 428 429 time(&t); 430 return (t); 431 } 432 #endif 433 434 435 #if !HAVE_STRERROR 436 /* 437 * Local version of strerror, if not available from the system. 438 */ 439 static char * strerror(int err) 440 { 441 static char buf[INT_STRLEN_BOUND(int)+12]; 442 #if HAVE_SYS_ERRLIST 443 extern char *sys_errlist[]; 444 extern int sys_nerr; 445 446 if (err < sys_nerr) 447 return sys_errlist[err]; 448 #endif 449 sprintf(buf, "Error %d", err); 450 return buf; 451 } 452 #endif 453 454 /* 455 * errno_message: Return an error message based on the value of "errno". 456 */ 457 public char * errno_message(constant char *filename) 458 { 459 char *p; 460 char *m; 461 size_t len; 462 #if HAVE_ERRNO 463 p = strerror(errno); 464 #else 465 p = "cannot open"; 466 #endif 467 len = strlen(filename) + strlen(p) + 3; 468 m = (char *) ecalloc(len, sizeof(char)); 469 SNPRINTF2(m, len, "%s: %s", filename, p); 470 return (m); 471 } 472 473 /* 474 * Return a description of a signal. 475 * The return value is good until the next call to this function. 476 */ 477 public constant char * signal_message(int sig) 478 { 479 static char sigbuf[sizeof("Signal ") + INT_STRLEN_BOUND(sig) + 1]; 480 #if HAVE_STRSIGNAL 481 constant char *description = strsignal(sig); 482 if (description) 483 return description; 484 #endif 485 sprintf(sigbuf, "Signal %d", sig); 486 return sigbuf; 487 } 488 489 /* 490 * Return (VAL * NUM) / DEN, where DEN is positive 491 * and min(VAL, NUM) <= DEN so the result cannot overflow. 492 * Round to the nearest integer, breaking ties by rounding to even. 493 */ 494 public uintmax umuldiv(uintmax val, uintmax num, uintmax den) 495 { 496 /* 497 * Like round(val * (double) num / den), but without rounding error. 498 * Overflow cannot occur, so there is no need for floating point. 499 */ 500 uintmax q = val / den; 501 uintmax r = val % den; 502 uintmax qnum = q * num; 503 uintmax rnum = r * num; 504 uintmax quot = qnum + rnum / den; 505 uintmax rem = rnum % den; 506 return quot + (den / 2 < rem + (quot & ~den & 1)); 507 } 508 509 /* 510 * Return the ratio of two POSITIONS, as a percentage. 511 * {{ Assumes a POSITION is a long int. }} 512 */ 513 public int percentage(POSITION num, POSITION den) 514 { 515 return (int) muldiv(num, 100, den); 516 } 517 518 /* 519 * Return the specified percentage of a POSITION. 520 * Assume (0 <= POS && 0 <= PERCENT <= 100 521 * && 0 <= FRACTION < (PERCENT == 100 ? 1 : NUM_FRAC_DENOM)), 522 * so the result cannot overflow. Round to even. 523 */ 524 public POSITION percent_pos(POSITION pos, int percent, long fraction) 525 { 526 /* 527 * Change from percent (parts per 100) 528 * to pctden (parts per 100 * NUM_FRAC_DENOM). 529 */ 530 POSITION pctden = (percent * NUM_FRAC_DENOM) + fraction; 531 532 return (POSITION) muldiv(pos, pctden, 100 * NUM_FRAC_DENOM); 533 } 534 535 #if !HAVE_STRCHR 536 /* 537 * strchr is used by regexp.c. 538 */ 539 char * strchr(char *s, char c) 540 { 541 for ( ; *s != '\0'; s++) 542 if (*s == c) 543 return (s); 544 if (c == '\0') 545 return (s); 546 return (NULL); 547 } 548 #endif 549 550 #if !HAVE_MEMCPY 551 void * memcpy(void *dst, void *src, size_t len) 552 { 553 char *dstp = (char *) dst; 554 char *srcp = (char *) src; 555 int i; 556 557 for (i = 0; i < len; i++) 558 dstp[i] = srcp[i]; 559 return (dst); 560 } 561 #endif 562 563 #ifdef _OSK_MWC32 564 565 /* 566 * This implements an ANSI-style intercept setup for Microware C 3.2 567 */ 568 public int os9_signal(int type, RETSIGTYPE (*handler)()) 569 { 570 intercept(handler); 571 } 572 573 #include <sgstat.h> 574 575 int isatty(int f) 576 { 577 struct sgbuf sgbuf; 578 579 if (_gs_opt(f, &sgbuf) < 0) 580 return -1; 581 return (sgbuf.sg_class == 0); 582 } 583 584 #endif 585 586 public void sleep_ms(int ms) 587 { 588 #if MSDOS_COMPILER==WIN32C 589 Sleep(ms); 590 #else 591 #if HAVE_NANOSLEEP 592 int sec = ms / 1000; 593 struct timespec t = { sec, (ms - sec*1000) * 1000000 }; 594 nanosleep(&t, NULL); 595 #else 596 #if HAVE_USLEEP 597 usleep(ms * 1000); 598 #else 599 sleep(ms / 1000 + (ms % 1000 != 0)); 600 #endif 601 #endif 602 #endif 603 } 604