1*0c995ed2Ssimonb /* $NetBSD: os.c,v 1.6 2023/10/06 07:31:30 simonb Exp $ */
220006a0bStron
320006a0bStron /*
4838f5788Ssimonb * Copyright (C) 1984-2023 Mark Nudelman
520006a0bStron *
620006a0bStron * You may distribute under the terms of either the GNU General Public
720006a0bStron * License or the Less License, as specified in the README file.
820006a0bStron *
9ec18bca0Stron * For more information, see the README file.
1020006a0bStron */
1120006a0bStron
1220006a0bStron
1320006a0bStron /*
1420006a0bStron * Operating system dependent routines.
1520006a0bStron *
1620006a0bStron * Most of the stuff in here is based on Unix, but an attempt
1720006a0bStron * has been made to make things work on other operating systems.
1820006a0bStron * This will sometimes result in a loss of functionality, unless
1920006a0bStron * someone rewrites code specifically for the new operating system.
2020006a0bStron *
2120006a0bStron * The makefile provides defines to decide whether various
2220006a0bStron * Unix features are present.
2320006a0bStron */
2420006a0bStron
2520006a0bStron #include "less.h"
2620006a0bStron #include <signal.h>
2720006a0bStron #include <setjmp.h>
28838f5788Ssimonb #if MSDOS_COMPILER==WIN32C
29838f5788Ssimonb #include <windows.h>
30838f5788Ssimonb #endif
3120006a0bStron #if HAVE_TIME_H
3220006a0bStron #include <time.h>
3320006a0bStron #endif
3420006a0bStron #if HAVE_ERRNO_H
3520006a0bStron #include <errno.h>
3620006a0bStron #endif
3720006a0bStron #if HAVE_VALUES_H
3820006a0bStron #include <values.h>
3920006a0bStron #endif
4020006a0bStron
41838f5788Ssimonb #if defined(__APPLE__)
42838f5788Ssimonb #include <sys/utsname.h>
43838f5788Ssimonb #endif
44838f5788Ssimonb
45838f5788Ssimonb #if HAVE_POLL && !MSDOS_COMPILER
46838f5788Ssimonb #define USE_POLL 1
47838f5788Ssimonb static int use_poll = TRUE;
4820006a0bStron #else
49838f5788Ssimonb #define USE_POLL 0
50838f5788Ssimonb #endif
51838f5788Ssimonb #if USE_POLL
52838f5788Ssimonb #include <poll.h>
53838f5788Ssimonb static int any_data = FALSE;
5420006a0bStron #endif
5520006a0bStron
5620006a0bStron /*
5720006a0bStron * BSD setjmp() saves (and longjmp() restores) the signal mask.
5820006a0bStron * This costs a system call or two per setjmp(), so if possible we clear the
5920006a0bStron * signal mask with sigsetmask(), and use _setjmp()/_longjmp() instead.
6020006a0bStron * On other systems, setjmp() doesn't affect the signal mask and so
6120006a0bStron * _setjmp() does not exist; we just use setjmp().
6220006a0bStron */
6320006a0bStron #if HAVE__SETJMP && HAVE_SIGSETMASK
6420006a0bStron #define SET_JUMP _setjmp
6520006a0bStron #define LONG_JUMP _longjmp
6620006a0bStron #else
6720006a0bStron #define SET_JUMP setjmp
6820006a0bStron #define LONG_JUMP longjmp
6920006a0bStron #endif
7020006a0bStron
7120006a0bStron public int reading;
72838f5788Ssimonb public int waiting_for_data;
73838f5788Ssimonb public int consecutive_nulls = 0;
7420006a0bStron
75838f5788Ssimonb /* Milliseconds to wait for data before displaying "waiting for data" message. */
76838f5788Ssimonb static int waiting_for_data_delay = 4000;
7720006a0bStron static jmp_buf read_label;
7820006a0bStron
7920006a0bStron extern int sigs;
80838f5788Ssimonb extern int ignore_eoi;
81838f5788Ssimonb extern int exit_F_on_close;
82838f5788Ssimonb extern int follow_mode;
83838f5788Ssimonb extern int scanning_eof;
84838f5788Ssimonb extern char intr_char;
85838f5788Ssimonb #if !MSDOS_COMPILER
86838f5788Ssimonb extern int tty;
87838f5788Ssimonb #endif
88838f5788Ssimonb #if LESSTEST
89838f5788Ssimonb extern char *ttyin_name;
90838f5788Ssimonb #endif /*LESSTEST*/
91838f5788Ssimonb
init_poll(void)92838f5788Ssimonb public void init_poll(void)
93838f5788Ssimonb {
94838f5788Ssimonb char *delay = lgetenv("LESS_DATA_DELAY");
95838f5788Ssimonb int idelay = (delay == NULL) ? 0 : atoi(delay);
96838f5788Ssimonb if (idelay > 0)
97838f5788Ssimonb waiting_for_data_delay = idelay;
98838f5788Ssimonb #if USE_POLL
99838f5788Ssimonb #if defined(__APPLE__)
100838f5788Ssimonb /* In old versions of MacOS, poll() does not work with /dev/tty. */
101838f5788Ssimonb struct utsname uts;
102838f5788Ssimonb if (uname(&uts) < 0 || lstrtoi(uts.release, NULL, 10) < 20)
103838f5788Ssimonb use_poll = FALSE;
104838f5788Ssimonb #endif
105838f5788Ssimonb #endif
106838f5788Ssimonb }
107838f5788Ssimonb
108838f5788Ssimonb #if USE_POLL
109838f5788Ssimonb /*
110838f5788Ssimonb * Check whether data is available, either from a file/pipe or from the tty.
111838f5788Ssimonb * Return READ_AGAIN if no data currently available, but caller should retry later.
112838f5788Ssimonb * Return READ_INTR to abort F command (forw_loop).
113838f5788Ssimonb * Return 0 if safe to read from fd.
114838f5788Ssimonb */
check_poll(int fd,int tty)115838f5788Ssimonb static int check_poll(int fd, int tty)
116838f5788Ssimonb {
117838f5788Ssimonb struct pollfd poller[2] = { { fd, POLLIN, 0 }, { tty, POLLIN, 0 } };
118838f5788Ssimonb int timeout = (waiting_for_data && !(scanning_eof && follow_mode == FOLLOW_NAME)) ? -1 : waiting_for_data_delay;
119838f5788Ssimonb if (!any_data)
120838f5788Ssimonb {
121838f5788Ssimonb /*
122838f5788Ssimonb * Don't do polling if no data has yet been received,
123838f5788Ssimonb * to allow a program piping data into less to have temporary
124838f5788Ssimonb * access to the tty (like sudo asking for a password).
125838f5788Ssimonb */
126838f5788Ssimonb return (0);
127838f5788Ssimonb }
128838f5788Ssimonb poll(poller, 2, timeout);
129838f5788Ssimonb #if LESSTEST
130838f5788Ssimonb if (ttyin_name == NULL) /* Check for ^X only on a real tty. */
131838f5788Ssimonb #endif /*LESSTEST*/
132838f5788Ssimonb {
133838f5788Ssimonb if (poller[1].revents & POLLIN)
134838f5788Ssimonb {
135838f5788Ssimonb LWCHAR ch = getchr();
136838f5788Ssimonb if (ch == intr_char)
137838f5788Ssimonb /* Break out of "waiting for data". */
138838f5788Ssimonb return (READ_INTR);
139838f5788Ssimonb ungetcc_back(ch);
140838f5788Ssimonb }
141838f5788Ssimonb }
142838f5788Ssimonb if (ignore_eoi && exit_F_on_close && (poller[0].revents & (POLLHUP|POLLIN)) == POLLHUP)
143838f5788Ssimonb /* Break out of F loop on HUP due to --exit-follow-on-close. */
144838f5788Ssimonb return (READ_INTR);
145838f5788Ssimonb if ((poller[0].revents & (POLLIN|POLLHUP|POLLERR)) == 0)
146838f5788Ssimonb /* No data available; let caller take action, then try again. */
147838f5788Ssimonb return (READ_AGAIN);
148838f5788Ssimonb /* There is data (or HUP/ERR) available. Safe to call read() without blocking. */
149838f5788Ssimonb return (0);
150838f5788Ssimonb }
151838f5788Ssimonb #endif /* USE_POLL */
152838f5788Ssimonb
supports_ctrl_x(void)153838f5788Ssimonb public int supports_ctrl_x(void)
154838f5788Ssimonb {
155838f5788Ssimonb #if USE_POLL
156838f5788Ssimonb return (use_poll);
157838f5788Ssimonb #else
158838f5788Ssimonb return (FALSE);
159838f5788Ssimonb #endif /* USE_POLL */
160838f5788Ssimonb }
16120006a0bStron
16220006a0bStron /*
16320006a0bStron * Like read() system call, but is deliberately interruptible.
16420006a0bStron * A call to intread() from a signal handler will interrupt
16520006a0bStron * any pending iread().
16620006a0bStron */
iread(int fd,unsigned char * buf,unsigned int len)167838f5788Ssimonb public int iread(int fd, unsigned char *buf, unsigned int len)
16820006a0bStron {
169838f5788Ssimonb int n;
17020006a0bStron
17120006a0bStron start:
17220006a0bStron #if MSDOS_COMPILER==WIN32C
17320006a0bStron if (ABORT_SIGS())
17420006a0bStron return (READ_INTR);
17520006a0bStron #else
17620006a0bStron #if MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC
17720006a0bStron if (kbhit())
17820006a0bStron {
17920006a0bStron int c;
18020006a0bStron
18120006a0bStron c = getch();
18220006a0bStron if (c == '\003')
18320006a0bStron return (READ_INTR);
18420006a0bStron ungetch(c);
18520006a0bStron }
18620006a0bStron #endif
18720006a0bStron #endif
188838f5788Ssimonb if (!reading && SET_JUMP(read_label))
18920006a0bStron {
19020006a0bStron /*
19120006a0bStron * We jumped here from intread.
19220006a0bStron */
19320006a0bStron reading = 0;
19420006a0bStron #if HAVE_SIGPROCMASK
19520006a0bStron {
19620006a0bStron sigset_t mask;
19720006a0bStron sigemptyset(&mask);
19820006a0bStron sigprocmask(SIG_SETMASK, &mask, NULL);
19920006a0bStron }
20020006a0bStron #else
20120006a0bStron #if HAVE_SIGSETMASK
20220006a0bStron sigsetmask(0);
20320006a0bStron #else
20420006a0bStron #ifdef _OSK
20520006a0bStron sigmask(~0);
20620006a0bStron #endif
20720006a0bStron #endif
20820006a0bStron #endif
209838f5788Ssimonb #if !MSDOS_COMPILER
210838f5788Ssimonb if (fd != tty && !ABORT_SIGS())
211838f5788Ssimonb /* Non-interrupt signal like SIGWINCH. */
212838f5788Ssimonb return (READ_AGAIN);
213838f5788Ssimonb #endif
21420006a0bStron return (READ_INTR);
21520006a0bStron }
21620006a0bStron
21720006a0bStron flush();
21820006a0bStron reading = 1;
21920006a0bStron #if MSDOS_COMPILER==DJGPPC
22020006a0bStron if (isatty(fd))
22120006a0bStron {
22220006a0bStron /*
22320006a0bStron * Don't try reading from a TTY until a character is
22420006a0bStron * available, because that makes some background programs
22520006a0bStron * believe DOS is busy in a way that prevents those
22620006a0bStron * programs from working while "less" waits.
227838f5788Ssimonb * {{ This code was added 12 Jan 2007; still needed? }}
22820006a0bStron */
22920006a0bStron fd_set readfds;
23020006a0bStron
23120006a0bStron FD_ZERO(&readfds);
23220006a0bStron FD_SET(fd, &readfds);
23320006a0bStron if (select(fd+1, &readfds, 0, 0, 0) == -1)
234838f5788Ssimonb {
235838f5788Ssimonb reading = 0;
236838f5788Ssimonb return (READ_ERR);
237838f5788Ssimonb }
23820006a0bStron }
23920006a0bStron #endif
240838f5788Ssimonb #if USE_POLL
241838f5788Ssimonb if (fd != tty && use_poll)
242838f5788Ssimonb {
243838f5788Ssimonb int ret = check_poll(fd, tty);
244838f5788Ssimonb if (ret != 0)
245838f5788Ssimonb {
246838f5788Ssimonb if (ret == READ_INTR)
247838f5788Ssimonb sigs |= S_INTERRUPT;
248838f5788Ssimonb reading = 0;
249838f5788Ssimonb return (ret);
250838f5788Ssimonb }
251838f5788Ssimonb }
252838f5788Ssimonb #else
253838f5788Ssimonb #if MSDOS_COMPILER==WIN32C
254838f5788Ssimonb if (win32_kbhit())
255838f5788Ssimonb {
256838f5788Ssimonb int c;
257838f5788Ssimonb
258838f5788Ssimonb c = WIN32getch();
259838f5788Ssimonb if (c == intr_char)
260838f5788Ssimonb {
261838f5788Ssimonb sigs |= S_INTERRUPT;
262838f5788Ssimonb reading = 0;
263838f5788Ssimonb return (READ_INTR);
264838f5788Ssimonb }
265838f5788Ssimonb WIN32ungetch(c);
266838f5788Ssimonb }
267838f5788Ssimonb #endif
268838f5788Ssimonb #endif
26920006a0bStron n = read(fd, buf, len);
270838f5788Ssimonb reading = 0;
27120006a0bStron #if 1
27220006a0bStron /*
27320006a0bStron * This is a kludge to workaround a problem on some systems
27420006a0bStron * where terminating a remote tty connection causes read() to
27520006a0bStron * start returning 0 forever, instead of -1.
27620006a0bStron */
27720006a0bStron {
27820006a0bStron if (!ignore_eoi)
27920006a0bStron {
28020006a0bStron if (n == 0)
28120006a0bStron consecutive_nulls++;
28220006a0bStron else
28320006a0bStron consecutive_nulls = 0;
28420006a0bStron if (consecutive_nulls > 20)
28520006a0bStron quit(QUIT_ERROR);
28620006a0bStron }
28720006a0bStron }
28820006a0bStron #endif
28920006a0bStron if (n < 0)
29020006a0bStron {
29120006a0bStron #if HAVE_ERRNO
29220006a0bStron /*
29320006a0bStron * Certain values of errno indicate we should just retry the read.
29420006a0bStron */
29520006a0bStron #if MUST_DEFINE_ERRNO
29620006a0bStron extern int errno;
29720006a0bStron #endif
29820006a0bStron #ifdef EINTR
29920006a0bStron if (errno == EINTR)
30020006a0bStron goto start;
30120006a0bStron #endif
30220006a0bStron #ifdef EAGAIN
30320006a0bStron if (errno == EAGAIN)
30420006a0bStron goto start;
30520006a0bStron #endif
30620006a0bStron #endif
307838f5788Ssimonb return (READ_ERR);
30820006a0bStron }
309838f5788Ssimonb #if USE_POLL
310838f5788Ssimonb if (fd != tty && n > 0)
311838f5788Ssimonb any_data = TRUE;
312838f5788Ssimonb #endif
31320006a0bStron return (n);
31420006a0bStron }
31520006a0bStron
31620006a0bStron /*
31720006a0bStron * Interrupt a pending iread().
31820006a0bStron */
intread(void)319838f5788Ssimonb public void intread(void)
32020006a0bStron {
32120006a0bStron LONG_JUMP(read_label, 1);
32220006a0bStron }
32320006a0bStron
32420006a0bStron /*
32520006a0bStron * Return the current time.
32620006a0bStron */
32720006a0bStron #if HAVE_TIME
get_time(void)328838f5788Ssimonb public time_type get_time(void)
32920006a0bStron {
33020006a0bStron time_type t;
33120006a0bStron
33220006a0bStron time(&t);
33320006a0bStron return (t);
33420006a0bStron }
33520006a0bStron #endif
33620006a0bStron
33720006a0bStron
33820006a0bStron #if !HAVE_STRERROR
33920006a0bStron /*
34020006a0bStron * Local version of strerror, if not available from the system.
34120006a0bStron */
strerror(int err)342838f5788Ssimonb static char * strerror(int err)
34320006a0bStron {
344838f5788Ssimonb static char buf[INT_STRLEN_BOUND(int)+12];
34520006a0bStron #if HAVE_SYS_ERRLIST
34620006a0bStron extern char *sys_errlist[];
34720006a0bStron extern int sys_nerr;
34820006a0bStron
34920006a0bStron if (err < sys_nerr)
35020006a0bStron return sys_errlist[err];
351838f5788Ssimonb #endif
35220006a0bStron sprintf(buf, "Error %d", err);
35320006a0bStron return buf;
35420006a0bStron }
35520006a0bStron #endif
35620006a0bStron
35720006a0bStron /*
35820006a0bStron * errno_message: Return an error message based on the value of "errno".
35920006a0bStron */
errno_message(char * filename)360838f5788Ssimonb public char * errno_message(char *filename)
36120006a0bStron {
362838f5788Ssimonb char *p;
363838f5788Ssimonb char *m;
36420006a0bStron int len;
36520006a0bStron #if HAVE_ERRNO
36620006a0bStron #if MUST_DEFINE_ERRNO
36720006a0bStron extern int errno;
36820006a0bStron #endif
36920006a0bStron p = strerror(errno);
37020006a0bStron #else
37120006a0bStron p = "cannot open";
37220006a0bStron #endif
373838f5788Ssimonb len = (int) (strlen(filename) + strlen(p) + 3);
37420006a0bStron m = (char *) ecalloc(len, sizeof(char));
37520006a0bStron SNPRINTF2(m, len, "%s: %s", filename, p);
37620006a0bStron return (m);
37720006a0bStron }
37820006a0bStron
379838f5788Ssimonb /*
380838f5788Ssimonb * Return a description of a signal.
381838f5788Ssimonb * The return value is good until the next call to this function.
382838f5788Ssimonb */
signal_message(int sig)383838f5788Ssimonb public char * signal_message(int sig)
38420006a0bStron {
385838f5788Ssimonb static char sigbuf[sizeof("Signal ") + INT_STRLEN_BOUND(sig) + 1];
386838f5788Ssimonb #if HAVE_STRSIGNAL
387838f5788Ssimonb char *description = strsignal(sig);
388838f5788Ssimonb if (description)
389838f5788Ssimonb return description;
39020006a0bStron #endif
391838f5788Ssimonb sprintf(sigbuf, "Signal %d", sig);
392838f5788Ssimonb return sigbuf;
393838f5788Ssimonb }
394838f5788Ssimonb
395838f5788Ssimonb /*
396838f5788Ssimonb * Return (VAL * NUM) / DEN, where DEN is positive
397838f5788Ssimonb * and min(VAL, NUM) <= DEN so the result cannot overflow.
398838f5788Ssimonb * Round to the nearest integer, breaking ties by rounding to even.
399838f5788Ssimonb */
muldiv(uintmax val,uintmax num,uintmax den)400838f5788Ssimonb public uintmax muldiv(uintmax val, uintmax num, uintmax den)
401838f5788Ssimonb {
402838f5788Ssimonb /*
403838f5788Ssimonb * Like round(val * (double) num / den), but without rounding error.
404838f5788Ssimonb * Overflow cannot occur, so there is no need for floating point.
405838f5788Ssimonb */
406838f5788Ssimonb uintmax q = val / den;
407838f5788Ssimonb uintmax r = val % den;
408838f5788Ssimonb uintmax qnum = q * num;
409838f5788Ssimonb uintmax rnum = r * num;
410838f5788Ssimonb uintmax quot = qnum + rnum / den;
411838f5788Ssimonb uintmax rem = rnum % den;
412838f5788Ssimonb return quot + (den / 2 < rem + (quot & ~den & 1));
41320006a0bStron }
41420006a0bStron
41520006a0bStron /*
41620006a0bStron * Return the ratio of two POSITIONS, as a percentage.
41720006a0bStron * {{ Assumes a POSITION is a long int. }}
41820006a0bStron */
percentage(POSITION num,POSITION den)419838f5788Ssimonb public int percentage(POSITION num, POSITION den)
42020006a0bStron {
42120006a0bStron return (int) muldiv(num, (POSITION) 100, den);
42220006a0bStron }
42320006a0bStron
42420006a0bStron /*
42520006a0bStron * Return the specified percentage of a POSITION.
426838f5788Ssimonb * Assume (0 <= POS && 0 <= PERCENT <= 100
427838f5788Ssimonb * && 0 <= FRACTION < (PERCENT == 100 ? 1 : NUM_FRAC_DENOM)),
428838f5788Ssimonb * so the result cannot overflow. Round to even.
42920006a0bStron */
percent_pos(POSITION pos,int percent,long fraction)430838f5788Ssimonb public POSITION percent_pos(POSITION pos, int percent, long fraction)
43120006a0bStron {
432838f5788Ssimonb /*
433838f5788Ssimonb * Change from percent (parts per 100)
434838f5788Ssimonb * to pctden (parts per 100 * NUM_FRAC_DENOM).
435838f5788Ssimonb */
436838f5788Ssimonb POSITION pctden = (percent * NUM_FRAC_DENOM) + fraction;
43720006a0bStron
438838f5788Ssimonb return (POSITION) muldiv(pos, pctden, 100 * (POSITION) NUM_FRAC_DENOM);
43920006a0bStron }
44020006a0bStron
44120006a0bStron #if !HAVE_STRCHR
44220006a0bStron /*
44320006a0bStron * strchr is used by regexp.c.
44420006a0bStron */
strchr(char * s,char c)445838f5788Ssimonb char * strchr(char *s, char c)
44620006a0bStron {
44720006a0bStron for ( ; *s != '\0'; s++)
44820006a0bStron if (*s == c)
44920006a0bStron return (s);
45020006a0bStron if (c == '\0')
45120006a0bStron return (s);
45220006a0bStron return (NULL);
45320006a0bStron }
45420006a0bStron #endif
45520006a0bStron
45620006a0bStron #if !HAVE_MEMCPY
memcpy(void * dst,void * src,int len)457838f5788Ssimonb void * memcpy(void *dst, void *src, int len)
45820006a0bStron {
45920006a0bStron char *dstp = (char *) dst;
46020006a0bStron char *srcp = (char *) src;
46120006a0bStron int i;
46220006a0bStron
46320006a0bStron for (i = 0; i < len; i++)
46420006a0bStron dstp[i] = srcp[i];
46520006a0bStron return (dst);
46620006a0bStron }
46720006a0bStron #endif
46820006a0bStron
46920006a0bStron #ifdef _OSK_MWC32
47020006a0bStron
47120006a0bStron /*
47220006a0bStron * This implements an ANSI-style intercept setup for Microware C 3.2
47320006a0bStron */
os9_signal(int type,RETSIGTYPE (* handler)())474838f5788Ssimonb public int os9_signal(int type, RETSIGTYPE (*handler)())
47520006a0bStron {
47620006a0bStron intercept(handler);
47720006a0bStron }
47820006a0bStron
47920006a0bStron #include <sgstat.h>
48020006a0bStron
isatty(int f)481838f5788Ssimonb int isatty(int f)
48220006a0bStron {
48320006a0bStron struct sgbuf sgbuf;
48420006a0bStron
48520006a0bStron if (_gs_opt(f, &sgbuf) < 0)
48620006a0bStron return -1;
48720006a0bStron return (sgbuf.sg_class == 0);
48820006a0bStron }
48920006a0bStron
49020006a0bStron #endif
491838f5788Ssimonb
sleep_ms(int ms)492838f5788Ssimonb public void sleep_ms(int ms)
493838f5788Ssimonb {
494838f5788Ssimonb #if MSDOS_COMPILER==WIN32C
495838f5788Ssimonb Sleep(ms);
496838f5788Ssimonb #else
497838f5788Ssimonb #if HAVE_NANOSLEEP
498838f5788Ssimonb int sec = ms / 1000;
499838f5788Ssimonb struct timespec t = { sec, (ms - sec*1000) * 1000000 };
500838f5788Ssimonb nanosleep(&t, NULL);
501838f5788Ssimonb #else
502838f5788Ssimonb #if HAVE_USLEEP
503838f5788Ssimonb usleep(ms);
504838f5788Ssimonb #else
505838f5788Ssimonb sleep(ms / 1000 + (ms % 1000 != 0));
506838f5788Ssimonb #endif
507838f5788Ssimonb #endif
508838f5788Ssimonb #endif
509838f5788Ssimonb }
510