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