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