xref: /freebsd/contrib/less/os.c (revision 4ac8f4067096a9d4a00e41cd53bd5c4fa295fd15)
1 /*
2  * Copyright (C) 1984-2022  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 HAVE_POLL && !MSDOS_COMPILER && !defined(__APPLE__)
40 #define USE_POLL 1
41 #else
42 #define USE_POLL 0
43 #endif
44 #if USE_POLL
45 #include <poll.h>
46 #endif
47 
48 /*
49  * BSD setjmp() saves (and longjmp() restores) the signal mask.
50  * This costs a system call or two per setjmp(), so if possible we clear the
51  * signal mask with sigsetmask(), and use _setjmp()/_longjmp() instead.
52  * On other systems, setjmp() doesn't affect the signal mask and so
53  * _setjmp() does not exist; we just use setjmp().
54  */
55 #if HAVE__SETJMP && HAVE_SIGSETMASK
56 #define SET_JUMP        _setjmp
57 #define LONG_JUMP       _longjmp
58 #else
59 #define SET_JUMP        setjmp
60 #define LONG_JUMP       longjmp
61 #endif
62 
63 public int reading;
64 public int consecutive_nulls = 0;
65 
66 static jmp_buf read_label;
67 
68 extern int sigs;
69 extern int ignore_eoi;
70 extern int exit_F_on_close;
71 #if !MSDOS_COMPILER
72 extern int tty;
73 #endif
74 
75 #if USE_POLL
76 /*
77  * Return true if one of the events has occurred on the specified file.
78  */
79 	static int
80 poll_events(fd, events)
81 	int fd;
82 	int events;
83 {
84 	struct pollfd poller = { fd, events, 0 };
85 	int n = poll(&poller, 1, 0);
86 	if (n <= 0)
87 		return 0;
88 	return (poller.revents & events);
89 }
90 #endif
91 
92 /*
93  * Like read() system call, but is deliberately interruptible.
94  * A call to intread() from a signal handler will interrupt
95  * any pending iread().
96  */
97 	public int
98 iread(fd, buf, len)
99 	int fd;
100 	unsigned char *buf;
101 	unsigned int len;
102 {
103 	int n;
104 
105 start:
106 #if MSDOS_COMPILER==WIN32C
107 	if (ABORT_SIGS())
108 		return (READ_INTR);
109 #else
110 #if MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC
111 	if (kbhit())
112 	{
113 		int c;
114 
115 		c = getch();
116 		if (c == '\003')
117 			return (READ_INTR);
118 		ungetch(c);
119 	}
120 #endif
121 #endif
122 	if (SET_JUMP(read_label))
123 	{
124 		/*
125 		 * We jumped here from intread.
126 		 */
127 		reading = 0;
128 #if HAVE_SIGPROCMASK
129 		{
130 		  sigset_t mask;
131 		  sigemptyset(&mask);
132 		  sigprocmask(SIG_SETMASK, &mask, NULL);
133 		}
134 #else
135 #if HAVE_SIGSETMASK
136 		sigsetmask(0);
137 #else
138 #ifdef _OSK
139 		sigmask(~0);
140 #endif
141 #endif
142 #endif
143 		return (READ_INTR);
144 	}
145 
146 	flush();
147 	reading = 1;
148 #if MSDOS_COMPILER==DJGPPC
149 	if (isatty(fd))
150 	{
151 		/*
152 		 * Don't try reading from a TTY until a character is
153 		 * available, because that makes some background programs
154 		 * believe DOS is busy in a way that prevents those
155 		 * programs from working while "less" waits.
156 		 */
157 		fd_set readfds;
158 
159 		FD_ZERO(&readfds);
160 		FD_SET(fd, &readfds);
161 		if (select(fd+1, &readfds, 0, 0, 0) == -1)
162 		{
163 			reading = 0;
164 			return (-1);
165 		}
166 	}
167 #endif
168 #if USE_POLL
169 	if (ignore_eoi && fd != tty)
170 	{
171 		int close_events = exit_F_on_close ? POLLERR|POLLHUP : POLLERR;
172 		if (poll_events(tty, POLLIN) && getchr() == CONTROL('X'))
173 		{
174 			sigs |= S_INTERRUPT;
175 			reading = 0;
176 			return (READ_INTR);
177 		}
178 		if (poll_events(fd, close_events))
179 		{
180 			sigs |= S_INTERRUPT;
181 			reading = 0;
182 			return (READ_INTR);
183 		}
184 	}
185 #else
186 #if MSDOS_COMPILER==WIN32C
187 	if (win32_kbhit() && WIN32getch() == CONTROL('X'))
188 	{
189 		sigs |= S_INTERRUPT;
190 		reading = 0;
191 		return (READ_INTR);
192 	}
193 #endif
194 #endif
195 	n = read(fd, buf, len);
196 	reading = 0;
197 #if 1
198 	/*
199 	 * This is a kludge to workaround a problem on some systems
200 	 * where terminating a remote tty connection causes read() to
201 	 * start returning 0 forever, instead of -1.
202 	 */
203 	{
204 		if (!ignore_eoi)
205 		{
206 			if (n == 0)
207 				consecutive_nulls++;
208 			else
209 				consecutive_nulls = 0;
210 			if (consecutive_nulls > 20)
211 				quit(QUIT_ERROR);
212 		}
213 	}
214 #endif
215 	if (n < 0)
216 	{
217 #if HAVE_ERRNO
218 		/*
219 		 * Certain values of errno indicate we should just retry the read.
220 		 */
221 #if MUST_DEFINE_ERRNO
222 		extern int errno;
223 #endif
224 #ifdef EINTR
225 		if (errno == EINTR)
226 			goto start;
227 #endif
228 #ifdef EAGAIN
229 		if (errno == EAGAIN)
230 			goto start;
231 #endif
232 #endif
233 		return (-1);
234 	}
235 	return (n);
236 }
237 
238 /*
239  * Interrupt a pending iread().
240  */
241 	public void
242 intread(VOID_PARAM)
243 {
244 	LONG_JUMP(read_label, 1);
245 }
246 
247 /*
248  * Return the current time.
249  */
250 #if HAVE_TIME
251 	public time_type
252 get_time(VOID_PARAM)
253 {
254 	time_type t;
255 
256 	time(&t);
257 	return (t);
258 }
259 #endif
260 
261 
262 #if !HAVE_STRERROR
263 /*
264  * Local version of strerror, if not available from the system.
265  */
266 	static char *
267 strerror(err)
268 	int err;
269 {
270 	static char buf[INT_STRLEN_BOUND(int)+12];
271 #if HAVE_SYS_ERRLIST
272 	extern char *sys_errlist[];
273 	extern int sys_nerr;
274 
275 	if (err < sys_nerr)
276 		return sys_errlist[err];
277 #endif
278 	sprintf(buf, "Error %d", err);
279 	return buf;
280 }
281 #endif
282 
283 /*
284  * errno_message: Return an error message based on the value of "errno".
285  */
286 	public char *
287 errno_message(filename)
288 	char *filename;
289 {
290 	char *p;
291 	char *m;
292 	int len;
293 #if HAVE_ERRNO
294 #if MUST_DEFINE_ERRNO
295 	extern int errno;
296 #endif
297 	p = strerror(errno);
298 #else
299 	p = "cannot open";
300 #endif
301 	len = (int) (strlen(filename) + strlen(p) + 3);
302 	m = (char *) ecalloc(len, sizeof(char));
303 	SNPRINTF2(m, len, "%s: %s", filename, p);
304 	return (m);
305 }
306 
307 /* #define HAVE_FLOAT 0 */
308 
309 	static POSITION
310 muldiv(val, num, den)
311 	POSITION val, num, den;
312 {
313 #if HAVE_FLOAT
314 	double v = (((double) val) * num) / den;
315 	return ((POSITION) (v + 0.5));
316 #else
317 	POSITION v = ((POSITION) val) * num;
318 
319 	if (v / num == val)
320 		/* No overflow */
321 		return (POSITION) (v / den);
322 	else
323 		/* Above calculation overflows;
324 		 * use a method that is less precise but won't overflow. */
325 		return (POSITION) (val / (den / num));
326 #endif
327 }
328 
329 /*
330  * Return the ratio of two POSITIONS, as a percentage.
331  * {{ Assumes a POSITION is a long int. }}
332  */
333 	public int
334 percentage(num, den)
335 	POSITION num;
336 	POSITION den;
337 {
338 	return (int) muldiv(num,  (POSITION) 100, den);
339 }
340 
341 /*
342  * Return the specified percentage of a POSITION.
343  */
344 	public POSITION
345 percent_pos(pos, percent, fraction)
346 	POSITION pos;
347 	int percent;
348 	long fraction;
349 {
350 	/* Change percent (parts per 100) to perden (parts per NUM_FRAC_DENOM). */
351 	POSITION perden = (percent * (NUM_FRAC_DENOM / 100)) + (fraction / 100);
352 
353 	if (perden == 0)
354 		return (0);
355 	return (POSITION) muldiv(pos, perden, (POSITION) NUM_FRAC_DENOM);
356 }
357 
358 #if !HAVE_STRCHR
359 /*
360  * strchr is used by regexp.c.
361  */
362 	char *
363 strchr(s, c)
364 	char *s;
365 	int c;
366 {
367 	for ( ;  *s != '\0';  s++)
368 		if (*s == c)
369 			return (s);
370 	if (c == '\0')
371 		return (s);
372 	return (NULL);
373 }
374 #endif
375 
376 #if !HAVE_MEMCPY
377 	VOID_POINTER
378 memcpy(dst, src, len)
379 	VOID_POINTER dst;
380 	VOID_POINTER src;
381 	int len;
382 {
383 	char *dstp = (char *) dst;
384 	char *srcp = (char *) src;
385 	int i;
386 
387 	for (i = 0;  i < len;  i++)
388 		dstp[i] = srcp[i];
389 	return (dst);
390 }
391 #endif
392 
393 #ifdef _OSK_MWC32
394 
395 /*
396  * This implements an ANSI-style intercept setup for Microware C 3.2
397  */
398 	public int
399 os9_signal(type, handler)
400 	int type;
401 	RETSIGTYPE (*handler)();
402 {
403 	intercept(handler);
404 }
405 
406 #include <sgstat.h>
407 
408 	int
409 isatty(f)
410 	int f;
411 {
412 	struct sgbuf sgbuf;
413 
414 	if (_gs_opt(f, &sgbuf) < 0)
415 		return -1;
416 	return (sgbuf.sg_class == 0);
417 }
418 
419 #endif
420 
421 	public void
422 sleep_ms(ms)
423 	int ms;
424 {
425 #if MSDOS_COMPILER==WIN32C
426 	Sleep(ms);
427 #else
428 #if HAVE_NANOSLEEP
429 	int sec = ms / 1000;
430 	struct timespec t = { sec, (ms - sec*1000) * 1000000 };
431 	nanosleep(&t, NULL);
432 #else
433 #if HAVE_USLEEP
434 	usleep(ms);
435 #else
436 	sleep((ms+999) / 1000);
437 #endif
438 #endif
439 #endif
440 }
441