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