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