Home | History | Annotate | Line # | Download | only in dist
os.c revision 1.5
      1 /*	$NetBSD: os.c,v 1.5 2023/10/06 05:49:49 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 #if !HAVE_STRERROR
    163 static char *strerror __P((int));
    164 #endif
    165 
    166 /*
    167  * Like read() system call, but is deliberately interruptible.
    168  * A call to intread() from a signal handler will interrupt
    169  * any pending iread().
    170  */
    171 public int iread(int fd, unsigned char *buf, unsigned int len)
    172 {
    173 	int n;
    174 
    175 start:
    176 #if MSDOS_COMPILER==WIN32C
    177 	if (ABORT_SIGS())
    178 		return (READ_INTR);
    179 #else
    180 #if MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC
    181 	if (kbhit())
    182 	{
    183 		int c;
    184 
    185 		c = getch();
    186 		if (c == '\003')
    187 			return (READ_INTR);
    188 		ungetch(c);
    189 	}
    190 #endif
    191 #endif
    192 	if (!reading && SET_JUMP(read_label))
    193 	{
    194 		/*
    195 		 * We jumped here from intread.
    196 		 */
    197 		reading = 0;
    198 #if HAVE_SIGPROCMASK
    199 		{
    200 		  sigset_t mask;
    201 		  sigemptyset(&mask);
    202 		  sigprocmask(SIG_SETMASK, &mask, NULL);
    203 		}
    204 #else
    205 #if HAVE_SIGSETMASK
    206 		sigsetmask(0);
    207 #else
    208 #ifdef _OSK
    209 		sigmask(~0);
    210 #endif
    211 #endif
    212 #endif
    213 #if !MSDOS_COMPILER
    214 		if (fd != tty && !ABORT_SIGS())
    215 			/* Non-interrupt signal like SIGWINCH. */
    216 			return (READ_AGAIN);
    217 #endif
    218 		return (READ_INTR);
    219 	}
    220 
    221 	flush();
    222 	reading = 1;
    223 #if MSDOS_COMPILER==DJGPPC
    224 	if (isatty(fd))
    225 	{
    226 		/*
    227 		 * Don't try reading from a TTY until a character is
    228 		 * available, because that makes some background programs
    229 		 * believe DOS is busy in a way that prevents those
    230 		 * programs from working while "less" waits.
    231 		 * {{ This code was added 12 Jan 2007; still needed? }}
    232 		 */
    233 		fd_set readfds;
    234 
    235 		FD_ZERO(&readfds);
    236 		FD_SET(fd, &readfds);
    237 		if (select(fd+1, &readfds, 0, 0, 0) == -1)
    238 		{
    239 			reading = 0;
    240 			return (READ_ERR);
    241 		}
    242 	}
    243 #endif
    244 #if USE_POLL
    245 	if (fd != tty && use_poll)
    246 	{
    247 		int ret = check_poll(fd, tty);
    248 		if (ret != 0)
    249 		{
    250 			if (ret == READ_INTR)
    251 				sigs |= S_INTERRUPT;
    252 			reading = 0;
    253 			return (ret);
    254 		}
    255 	}
    256 #else
    257 #if MSDOS_COMPILER==WIN32C
    258 	if (win32_kbhit())
    259 	{
    260 		int c;
    261 
    262 		c = WIN32getch();
    263 		if (c == intr_char)
    264 		{
    265 			sigs |= S_INTERRUPT;
    266 			reading = 0;
    267 			return (READ_INTR);
    268 		}
    269 		WIN32ungetch(c);
    270 	}
    271 #endif
    272 #endif
    273 	n = read(fd, buf, len);
    274 	reading = 0;
    275 #if 1
    276 	/*
    277 	 * This is a kludge to workaround a problem on some systems
    278 	 * where terminating a remote tty connection causes read() to
    279 	 * start returning 0 forever, instead of -1.
    280 	 */
    281 	{
    282 		if (!ignore_eoi)
    283 		{
    284 			if (n == 0)
    285 				consecutive_nulls++;
    286 			else
    287 				consecutive_nulls = 0;
    288 			if (consecutive_nulls > 20)
    289 				quit(QUIT_ERROR);
    290 		}
    291 	}
    292 #endif
    293 	if (n < 0)
    294 	{
    295 #if HAVE_ERRNO
    296 		/*
    297 		 * Certain values of errno indicate we should just retry the read.
    298 		 */
    299 #if MUST_DEFINE_ERRNO
    300 		extern int errno;
    301 #endif
    302 #ifdef EINTR
    303 		if (errno == EINTR)
    304 			goto start;
    305 #endif
    306 #ifdef EAGAIN
    307 		if (errno == EAGAIN)
    308 			goto start;
    309 #endif
    310 #endif
    311 		return (READ_ERR);
    312 	}
    313 #if USE_POLL
    314 	if (fd != tty && n > 0)
    315 		any_data = TRUE;
    316 #endif
    317 	return (n);
    318 }
    319 
    320 /*
    321  * Interrupt a pending iread().
    322  */
    323 public void intread(void)
    324 {
    325 	LONG_JUMP(read_label, 1);
    326 }
    327 
    328 /*
    329  * Return the current time.
    330  */
    331 #if HAVE_TIME
    332 public time_type get_time(void)
    333 {
    334 	time_type t;
    335 
    336 	time(&t);
    337 	return (t);
    338 }
    339 #endif
    340 
    341 
    342 #if !HAVE_STRERROR
    343 /*
    344  * Local version of strerror, if not available from the system.
    345  */
    346 static char * strerror(int err)
    347 {
    348 	static char buf[INT_STRLEN_BOUND(int)+12];
    349 #if HAVE_SYS_ERRLIST
    350 	extern char *sys_errlist[];
    351 	extern int sys_nerr;
    352 
    353 	if (err < sys_nerr)
    354 		return sys_errlist[err];
    355 #endif
    356 	sprintf(buf, "Error %d", err);
    357 	return buf;
    358 }
    359 #endif
    360 
    361 /*
    362  * errno_message: Return an error message based on the value of "errno".
    363  */
    364 public char * errno_message(char *filename)
    365 {
    366 	char *p;
    367 	char *m;
    368 	int len;
    369 #if HAVE_ERRNO
    370 #if MUST_DEFINE_ERRNO
    371 	extern int errno;
    372 #endif
    373 	p = strerror(errno);
    374 #else
    375 	p = "cannot open";
    376 #endif
    377 	len = (int) (strlen(filename) + strlen(p) + 3);
    378 	m = (char *) ecalloc(len, sizeof(char));
    379 	SNPRINTF2(m, len, "%s: %s", filename, p);
    380 	return (m);
    381 }
    382 
    383 /*
    384  * Return a description of a signal.
    385  * The return value is good until the next call to this function.
    386  */
    387 public char * signal_message(int sig)
    388 {
    389 	static char sigbuf[sizeof("Signal ") + INT_STRLEN_BOUND(sig) + 1];
    390 #if HAVE_STRSIGNAL
    391 	char *description = strsignal(sig);
    392 	if (description)
    393 		return description;
    394 #endif
    395 	sprintf(sigbuf, "Signal %d", sig);
    396 	return sigbuf;
    397 }
    398 
    399 /*
    400  * Return (VAL * NUM) / DEN, where DEN is positive
    401  * and min(VAL, NUM) <= DEN so the result cannot overflow.
    402  * Round to the nearest integer, breaking ties by rounding to even.
    403  */
    404 public uintmax muldiv(uintmax val, uintmax num, uintmax den)
    405 {
    406 	/*
    407 	 * Like round(val * (double) num / den), but without rounding error.
    408 	 * Overflow cannot occur, so there is no need for floating point.
    409 	 */
    410 	uintmax q = val / den;
    411 	uintmax r = val % den;
    412 	uintmax qnum = q * num;
    413 	uintmax rnum = r * num;
    414 	uintmax quot = qnum + rnum / den;
    415 	uintmax rem = rnum % den;
    416 	return quot + (den / 2 < rem + (quot & ~den & 1));
    417 }
    418 
    419 /*
    420  * Return the ratio of two POSITIONS, as a percentage.
    421  * {{ Assumes a POSITION is a long int. }}
    422  */
    423 public int percentage(POSITION num, POSITION den)
    424 {
    425 	return (int) muldiv(num,  (POSITION) 100, den);
    426 }
    427 
    428 /*
    429  * Return the specified percentage of a POSITION.
    430  * Assume (0 <= POS && 0 <= PERCENT <= 100
    431  *	   && 0 <= FRACTION < (PERCENT == 100 ? 1 : NUM_FRAC_DENOM)),
    432  * so the result cannot overflow.  Round to even.
    433  */
    434 public POSITION percent_pos(POSITION pos, int percent, long fraction)
    435 {
    436 	/*
    437 	 * Change from percent (parts per 100)
    438 	 * to pctden (parts per 100 * NUM_FRAC_DENOM).
    439 	 */
    440 	POSITION pctden = (percent * NUM_FRAC_DENOM) + fraction;
    441 
    442 	return (POSITION) muldiv(pos, pctden, 100 * (POSITION) NUM_FRAC_DENOM);
    443 }
    444 
    445 #if !HAVE_STRCHR
    446 /*
    447  * strchr is used by regexp.c.
    448  */
    449 char * strchr(char *s, char c)
    450 {
    451 	for ( ;  *s != '\0';  s++)
    452 		if (*s == c)
    453 			return (s);
    454 	if (c == '\0')
    455 		return (s);
    456 	return (NULL);
    457 }
    458 #endif
    459 
    460 #if !HAVE_MEMCPY
    461 void * memcpy(void *dst, void *src, int len)
    462 {
    463 	char *dstp = (char *) dst;
    464 	char *srcp = (char *) src;
    465 	int i;
    466 
    467 	for (i = 0;  i < len;  i++)
    468 		dstp[i] = srcp[i];
    469 	return (dst);
    470 }
    471 #endif
    472 
    473 #ifdef _OSK_MWC32
    474 
    475 /*
    476  * This implements an ANSI-style intercept setup for Microware C 3.2
    477  */
    478 public int os9_signal(int type, RETSIGTYPE (*handler)())
    479 {
    480 	intercept(handler);
    481 }
    482 
    483 #include <sgstat.h>
    484 
    485 int isatty(int f)
    486 {
    487 	struct sgbuf sgbuf;
    488 
    489 	if (_gs_opt(f, &sgbuf) < 0)
    490 		return -1;
    491 	return (sgbuf.sg_class == 0);
    492 }
    493 
    494 #endif
    495 
    496 public void sleep_ms(int ms)
    497 {
    498 #if MSDOS_COMPILER==WIN32C
    499 	Sleep(ms);
    500 #else
    501 #if HAVE_NANOSLEEP
    502 	int sec = ms / 1000;
    503 	struct timespec t = { sec, (ms - sec*1000) * 1000000 };
    504 	nanosleep(&t, NULL);
    505 #else
    506 #if HAVE_USLEEP
    507 	usleep(ms);
    508 #else
    509 	sleep(ms / 1000 + (ms % 1000 != 0));
    510 #endif
    511 #endif
    512 #endif
    513 }
    514