Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/less/os.c
104899 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 && !defined(__MVS__)
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 lbool 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 LESS_IREAD_TTY
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
}
167
}
168
if (ignore_eoi && exit_F_on_close && (poller[0].revents & (POLLHUP|POLLIN)) == POLLHUP)
169
/* Break out of F loop on HUP due to --exit-follow-on-close. */
170
return (READ_INTR);
171
if ((poller[0].revents & (POLLIN|POLLHUP|POLLERR)) == 0)
172
/* No data available; let caller take action, then try again. */
173
return (READ_AGAIN);
174
/* There is data (or HUP/ERR) available. Safe to call read() without blocking. */
175
return (0);
176
}
177
#endif /* USE_POLL */
178
179
/*
180
* Is a character available to be read from the tty?
181
*/
182
public lbool ttyin_ready(void)
183
{
184
#if MSDOS_COMPILER==WIN32C
185
return win32_kbhit();
186
#else
187
#if MSDOS_COMPILER
188
return kbhit();
189
#else
190
#if USE_POLL
191
#if LESSTEST
192
if (is_lesstest())
193
return FALSE;
194
#endif /*LESSTEST*/
195
if (!use_poll)
196
return FALSE;
197
{
198
/* {{ assert LESS_IREAD_TTY }} */
199
struct pollfd poller[1] = { { tty, POLLIN, 0 } };
200
poll(poller, 1, 0);
201
return ((poller[0].revents & POLLIN) != 0);
202
}
203
#else
204
return FALSE;
205
#endif
206
#endif
207
#endif
208
}
209
210
public lbool supports_ctrl_x(void)
211
{
212
#if MSDOS_COMPILER==WIN32C
213
return (TRUE);
214
#else
215
#if USE_POLL
216
return (use_poll);
217
#else
218
return (FALSE);
219
#endif /* USE_POLL */
220
#endif /* MSDOS_COMPILER==WIN32C */
221
}
222
223
/*
224
* Like read() system call, but is deliberately interruptible.
225
* A call to intio() from a signal handler will interrupt
226
* any pending iread().
227
*/
228
public ssize_t iread(int fd, unsigned char *buf, size_t len)
229
{
230
ssize_t n;
231
232
start:
233
#if MSDOS_COMPILER==WIN32C
234
if (ABORT_SIGS())
235
return (READ_INTR);
236
#else
237
#if MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC
238
if (kbhit())
239
{
240
int c;
241
242
c = getch();
243
if (c == '\003')
244
return (READ_INTR);
245
ungetch(c);
246
}
247
#endif
248
#endif
249
if (!reading && SET_JUMP(read_label))
250
{
251
/*
252
* We jumped here from intio.
253
*/
254
reading = FALSE;
255
#if HAVE_SIGPROCMASK
256
{
257
sigset_t mask;
258
sigemptyset(&mask);
259
sigprocmask(SIG_SETMASK, &mask, NULL);
260
}
261
#else
262
#if HAVE_SIGSETMASK
263
sigsetmask(0);
264
#else
265
#ifdef _OSK
266
sigmask(~0);
267
#endif
268
#endif
269
#endif
270
#if !MSDOS_COMPILER /* {{ LESS_IREAD_TTY? }} */
271
if (fd != tty && !ABORT_SIGS())
272
/* Non-interrupt signal like SIGWINCH. */
273
return (READ_AGAIN);
274
#endif
275
return (READ_INTR);
276
}
277
278
flush();
279
reading = TRUE;
280
#if MSDOS_COMPILER==DJGPPC
281
if (isatty(fd))
282
{
283
/*
284
* Don't try reading from a TTY until a character is
285
* available, because that makes some background programs
286
* believe DOS is busy in a way that prevents those
287
* programs from working while "less" waits.
288
* {{ This code was added 12 Jan 2007; still needed? }}
289
*/
290
fd_set readfds;
291
292
FD_ZERO(&readfds);
293
FD_SET(fd, &readfds);
294
if (select(fd+1, &readfds, 0, 0, 0) == -1)
295
{
296
reading = FALSE;
297
return (READ_ERR);
298
}
299
}
300
#endif
301
#if USE_POLL
302
if (is_tty && fd != tty && use_poll && !(quit_if_one_screen && one_screen))
303
{
304
int ret = check_poll(fd, tty);
305
if (ret != 0)
306
{
307
if (ret == READ_INTR)
308
sigs |= S_SWINTERRUPT;
309
reading = FALSE;
310
return (ret);
311
}
312
}
313
#else
314
#if MSDOS_COMPILER==WIN32C
315
if (!(quit_if_one_screen && one_screen) && win32_kbhit2(TRUE))
316
{
317
int c = WIN32getch();
318
if (c == CONTROL('C') || c == intr_char)
319
{
320
sigs |= S_SWINTERRUPT;
321
reading = FALSE;
322
return (READ_INTR);
323
}
324
WIN32ungetch(c);
325
}
326
#endif
327
#endif
328
n = read(fd, buf, len);
329
reading = FALSE;
330
#if 0
331
/*
332
* This is a kludge to workaround a problem on some systems
333
* where terminating a remote tty connection causes read() to
334
* start returning 0 forever, instead of -1.
335
*/
336
{
337
if (!ignore_eoi)
338
{
339
if (n == 0)
340
consecutive_nulls++;
341
else
342
consecutive_nulls = 0;
343
if (consecutive_nulls > 20)
344
quit(QUIT_ERROR);
345
}
346
}
347
#endif
348
if (n < 0)
349
{
350
#if HAVE_ERRNO
351
/*
352
* Certain values of errno indicate we should just retry the read.
353
*/
354
#ifdef EINTR
355
if (errno == EINTR)
356
goto start;
357
#endif
358
#ifdef EAGAIN
359
if (errno == EAGAIN)
360
goto start;
361
#endif
362
#endif
363
return (READ_ERR);
364
}
365
#if LESS_IREAD_TTY
366
if (fd != tty)
367
#endif
368
{
369
if (n > 0)
370
polling_ok();
371
}
372
return (n);
373
}
374
375
/*
376
* Like open() system call, but is interruptible.
377
*/
378
public int iopen(constant char *filename, int flags)
379
{
380
int r;
381
while (!opening && SET_JUMP(open_label))
382
{
383
opening = FALSE;
384
if (sigs & (S_INTERRUPT|S_SWINTERRUPT))
385
{
386
sigs = 0;
387
#if HAVE_SETTABLE_ERRNO
388
#ifdef EINTR
389
errno = EINTR;
390
#endif
391
#endif
392
return -1;
393
}
394
psignals(); /* Handle S_STOP or S_WINCH */
395
}
396
opening = TRUE;
397
r = open(filename, flags);
398
opening = FALSE;
399
return r;
400
}
401
402
/*
403
* Interrupt a pending iopen() or iread().
404
*/
405
public void intio(void)
406
{
407
if (opening)
408
{
409
LONG_JUMP(open_label, 1);
410
}
411
if (reading)
412
{
413
LONG_JUMP(read_label, 1);
414
}
415
}
416
417
/*
418
* We can start polling the input file.
419
*/
420
public void polling_ok(void)
421
{
422
#if USE_POLL
423
any_data = TRUE;
424
#endif
425
}
426
427
/*
428
* Return the current time.
429
*/
430
#if HAVE_TIME
431
public time_type get_time(void)
432
{
433
time_type t;
434
435
time(&t);
436
return (t);
437
}
438
#endif
439
440
441
#if !HAVE_STRERROR
442
/*
443
* Local version of strerror, if not available from the system.
444
*/
445
static char * strerror(int err)
446
{
447
static char buf[INT_STRLEN_BOUND(int)+12];
448
#if HAVE_SYS_ERRLIST
449
extern char *sys_errlist[];
450
extern int sys_nerr;
451
452
if (err < sys_nerr)
453
return sys_errlist[err];
454
#endif
455
sprintf(buf, "Error %d", err);
456
return buf;
457
}
458
#endif
459
460
/*
461
* errno_message: Return an error message based on the value of "errno".
462
*/
463
public char * errno_message(constant char *filename)
464
{
465
char *p;
466
char *m;
467
size_t len;
468
#if HAVE_ERRNO
469
p = strerror(errno);
470
#else
471
p = "cannot open";
472
#endif
473
len = strlen(filename) + strlen(p) + 3;
474
m = (char *) ecalloc(len, sizeof(char));
475
SNPRINTF2(m, len, "%s: %s", filename, p);
476
return (m);
477
}
478
479
/*
480
* Return a description of a signal.
481
* The return value is good until the next call to this function.
482
*/
483
public constant char * signal_message(int sig)
484
{
485
static char sigbuf[sizeof("Signal ") + INT_STRLEN_BOUND(sig) + 1];
486
#if HAVE_STRSIGNAL
487
constant char *description = strsignal(sig);
488
if (description)
489
return description;
490
#endif
491
sprintf(sigbuf, "Signal %d", sig);
492
return sigbuf;
493
}
494
495
/*
496
* Return (VAL * NUM) / DEN, where DEN is positive
497
* and min(VAL, NUM) <= DEN so the result cannot overflow.
498
* Round to the nearest integer, breaking ties by rounding to even.
499
*/
500
public uintmax umuldiv(uintmax val, uintmax num, uintmax den)
501
{
502
/*
503
* Like round(val * (double) num / den), but without rounding error.
504
* Overflow cannot occur, so there is no need for floating point.
505
*/
506
uintmax q = val / den;
507
uintmax r = val % den;
508
uintmax qnum = q * num;
509
uintmax rnum = r * num;
510
uintmax quot = qnum + rnum / den;
511
uintmax rem = rnum % den;
512
return quot + (den / 2 < rem + (quot & ~den & 1));
513
}
514
515
/*
516
* Return the ratio of two POSITIONS, as a percentage.
517
* {{ Assumes a POSITION is a long int. }}
518
*/
519
public int percentage(POSITION num, POSITION den)
520
{
521
return (int) muldiv(num, 100, den);
522
}
523
524
/*
525
* Return the specified percentage of a POSITION.
526
* Assume (0 <= POS && 0 <= PERCENT <= 100
527
* && 0 <= FRACTION < (PERCENT == 100 ? 1 : NUM_FRAC_DENOM)),
528
* so the result cannot overflow. Round to even.
529
*/
530
public POSITION percent_pos(POSITION pos, int percent, long fraction)
531
{
532
/*
533
* Change from percent (parts per 100)
534
* to pctden (parts per 100 * NUM_FRAC_DENOM).
535
*/
536
POSITION pctden = (percent * NUM_FRAC_DENOM) + fraction;
537
538
return (POSITION) muldiv(pos, pctden, 100 * NUM_FRAC_DENOM);
539
}
540
541
#if !HAVE_STRCHR
542
/*
543
* strchr is used by regexp.c.
544
*/
545
char * strchr(char *s, char c)
546
{
547
for ( ; *s != '\0'; s++)
548
if (*s == c)
549
return (s);
550
if (c == '\0')
551
return (s);
552
return (NULL);
553
}
554
#endif
555
556
#if !HAVE_MEMCPY
557
void * memcpy(void *dst, constant void *src, size_t len)
558
{
559
char *dstp = (char *) dst;
560
char *srcp = (char *) src;
561
int i;
562
563
for (i = 0; i < len; i++)
564
dstp[i] = srcp[i];
565
return (dst);
566
}
567
#endif
568
569
#if !HAVE_STRSTR
570
char * strstr(constant char *haystack, constant char *needle)
571
{
572
if (*needle == '\0')
573
return (char *) haystack;
574
for (; *haystack; haystack++) {
575
constant char *h = haystack;
576
constant char *n = needle;
577
while (*h != '\0' && *n != '\0' && *h == *n) {
578
h++;
579
n++;
580
}
581
if (*n == '\0')
582
return (char *) haystack;
583
}
584
return NULL;
585
}
586
#endif
587
588
#ifdef _OSK_MWC32
589
590
/*
591
* This implements an ANSI-style intercept setup for Microware C 3.2
592
*/
593
public int os9_signal(int type, RETSIGTYPE (*handler)())
594
{
595
intercept(handler);
596
}
597
598
#include <sgstat.h>
599
600
int isatty(int f)
601
{
602
struct sgbuf sgbuf;
603
604
if (_gs_opt(f, &sgbuf) < 0)
605
return -1;
606
return (sgbuf.sg_class == 0);
607
}
608
609
#endif
610
611
public void sleep_ms(int ms)
612
{
613
#if MSDOS_COMPILER==WIN32C
614
Sleep(ms);
615
#else
616
#if HAVE_NANOSLEEP
617
int sec = ms / 1000;
618
struct timespec t = { sec, (ms - sec*1000) * 1000000 };
619
nanosleep(&t, NULL);
620
#else
621
#if HAVE_USLEEP
622
usleep(ms * 1000);
623
#else
624
sleep(ms / 1000 + (ms % 1000 != 0));
625
#endif
626
#endif
627
#endif
628
}
629
630