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