Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/less/main.c
103834 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
* Entry point, initialization, miscellaneous routines.
13
*/
14
15
#include "less.h"
16
#if MSDOS_COMPILER==WIN32C
17
#define WIN32_LEAN_AND_MEAN
18
#include <windows.h>
19
20
#if defined(__MINGW32__) || defined(_MSC_VER)
21
#include <locale.h>
22
#include <shellapi.h>
23
#endif
24
25
public unsigned less_acp = CP_ACP;
26
#endif
27
28
#include "option.h"
29
30
public char * every_first_cmd = NULL;
31
public lbool new_file;
32
public int is_tty;
33
public IFILE curr_ifile = NULL_IFILE;
34
public IFILE old_ifile = NULL_IFILE;
35
public struct scrpos initial_scrpos;
36
public POSITION start_attnpos = NULL_POSITION;
37
public POSITION end_attnpos = NULL_POSITION;
38
public int wscroll;
39
public constant char *progname;
40
public lbool quitting = FALSE;
41
public int dohelp;
42
public char * init_header = NULL;
43
public char * no_config = NULL;
44
static int secure_allow_features;
45
46
#if LOGFILE
47
public int logfile = -1;
48
public lbool force_logfile = FALSE;
49
public char * namelogfile = NULL;
50
#endif
51
52
#if EDITOR
53
public constant char * editor;
54
public constant char * editproto;
55
#endif
56
57
#if TAGS
58
extern char * tags;
59
extern char * tagoption;
60
extern int jump_sline;
61
#endif
62
63
#if HAVE_TIME
64
public time_type less_start_time;
65
#endif
66
67
#ifdef WIN32
68
static wchar_t consoleTitle[256];
69
#endif
70
71
public int one_screen;
72
extern int less_is_more;
73
extern lbool missing_cap;
74
extern int know_dumb;
75
extern int quit_if_one_screen;
76
extern int no_init;
77
extern int errmsgs;
78
extern int redraw_on_quit;
79
extern int term_addrs;
80
extern lbool first_time;
81
extern lbool term_init_ever;
82
83
#if MSDOS_COMPILER==WIN32C && (defined(__MINGW32__) || defined(_MSC_VER))
84
/* malloc'ed 0-terminated utf8 of 0-terminated wide ws, or null on errors */
85
static char *utf8_from_wide(constant wchar_t *ws)
86
{
87
char *u8 = NULL;
88
int n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
89
if (n > 0)
90
{
91
u8 = ecalloc(n, sizeof(char));
92
WideCharToMultiByte(CP_UTF8, 0, ws, -1, u8, n, NULL, NULL);
93
}
94
return u8;
95
}
96
97
/*
98
* similar to using UTF8 manifest to make the ANSI APIs UTF8, but dynamically
99
* with setlocale. unlike the manifest, argv and environ are already ACP, so
100
* make them UTF8. Additionally, this affects only the libc/crt API, and so
101
* e.g. fopen filename becomes UTF-8, but CreateFileA filename remains CP_ACP.
102
* CP_ACP remains the original codepage - use the dynamic less_acp instead.
103
* effective on win 10 1803 or later when compiled with ucrt, else no-op.
104
*/
105
static void try_utf8_locale(int *pargc, constant char ***pargv)
106
{
107
char *locale_orig = strdup(setlocale(LC_ALL, NULL));
108
wchar_t **wargv = NULL, *wenv, *wp;
109
constant char **u8argv;
110
char *u8e;
111
int i, n;
112
113
if (!setlocale(LC_ALL, ".UTF8"))
114
goto cleanup; /* not win10 1803+ or not ucrt */
115
116
/*
117
* wargv is before glob expansion. some ucrt builds may expand globs
118
* before main is entered, so n may be smaller than the original argc.
119
* that's ok, because later code at main expands globs anyway.
120
*/
121
wargv = CommandLineToArgvW(GetCommandLineW(), &n);
122
if (!wargv)
123
goto bad_args;
124
125
u8argv = (constant char **) ecalloc(n + 1, sizeof(char *));
126
for (i = 0; i < n; ++i)
127
{
128
if (!(u8argv[i] = utf8_from_wide(wargv[i])))
129
goto bad_args;
130
}
131
u8argv[n] = 0;
132
133
less_acp = CP_UTF8;
134
*pargc = n;
135
*pargv = u8argv; /* leaked on exit */
136
137
/* convert wide env to utf8 where we can, but don't abort on errors */
138
if ((wenv = GetEnvironmentStringsW()))
139
{
140
for (wp = wenv; *wp; wp += wcslen(wp) + 1)
141
{
142
if ((u8e = utf8_from_wide(wp)))
143
_putenv(u8e);
144
free(u8e); /* windows putenv makes a copy */
145
}
146
FreeEnvironmentStringsW(wenv);
147
}
148
149
goto cleanup;
150
151
bad_args:
152
error("WARNING: cannot use unicode arguments", NULL_PARG);
153
setlocale(LC_ALL, locale_orig);
154
155
cleanup:
156
free(locale_orig);
157
LocalFree(wargv);
158
}
159
#endif
160
161
#if !SECURE
162
static int security_feature_error(constant char *type, size_t len, constant char *name)
163
{
164
PARG parg;
165
size_t msglen = len + strlen(type) + 64;
166
char *msg = ecalloc(msglen, sizeof(char));
167
SNPRINTF3(msg, msglen, "LESSSECURE_ALLOW: %s feature name \"%.*s\"", type, (int) len, name);
168
parg.p_string = msg;
169
error("%s", &parg);
170
free(msg);
171
return 0;
172
}
173
174
/*
175
* Return the SF_xxx value of a secure feature given the name of the feature.
176
*/
177
static int security_feature(constant char *name, size_t len)
178
{
179
struct secure_feature { constant char *name; int sf_value; };
180
static struct secure_feature features[] = {
181
{ "edit", SF_EDIT },
182
{ "examine", SF_EXAMINE },
183
{ "glob", SF_GLOB },
184
{ "history", SF_HISTORY },
185
{ "lesskey", SF_LESSKEY },
186
{ "lessopen", SF_LESSOPEN },
187
{ "logfile", SF_LOGFILE },
188
{ "osc8", SF_OSC8_OPEN },
189
{ "pipe", SF_PIPE },
190
{ "shell", SF_SHELL },
191
{ "stop", SF_STOP },
192
{ "tags", SF_TAGS },
193
};
194
int i;
195
int match = -1;
196
197
for (i = 0; i < countof(features); i++)
198
{
199
if (strncmp(features[i].name, name, len) == 0)
200
{
201
if (match >= 0) /* name is ambiguous */
202
return security_feature_error("ambiguous", len, name);
203
match = i;
204
}
205
}
206
if (match < 0)
207
return security_feature_error("invalid", len, name);
208
return features[match].sf_value;
209
}
210
211
static lbool set_security_feature(constant char *word, size_t wlen, void *arg)
212
{
213
secure_allow_features |= security_feature(word, wlen);
214
return TRUE;
215
}
216
#endif /* !SECURE */
217
218
/*
219
* Set the secure_allow_features bitmask, which controls
220
* whether certain secure features are allowed.
221
*/
222
static void init_secure(void)
223
{
224
#if SECURE
225
secure_allow_features = 0;
226
#else
227
constant char *str = lgetenv("LESSSECURE");
228
if (isnullenv(str))
229
secure_allow_features = ~0; /* allow everything */
230
else
231
secure_allow_features = 0; /* allow nothing */
232
233
str = lgetenv("LESSSECURE_ALLOW");
234
if (!isnullenv(str))
235
parse_csl(set_security_feature, str, NULL);
236
#endif
237
}
238
239
/*
240
* Entry point.
241
*/
242
int main(int argc, constant char *argv[])
243
{
244
IFILE ifile;
245
constant char *s;
246
int i;
247
struct xbuffer xfiles;
248
constant int *files;
249
size_t num_files;
250
size_t f;
251
lbool end_opts = FALSE;
252
lbool posixly_correct;
253
254
no_config = getenv("LESSNOCONFIG");
255
256
#if MSDOS_COMPILER==WIN32C && (defined(__MINGW32__) || defined(_MSC_VER))
257
if (GetACP() != CP_UTF8) /* not using a UTF-8 manifest */
258
try_utf8_locale(&argc, &argv);
259
#endif
260
261
#ifdef __EMX__
262
_response(&argc, &argv);
263
_wildcard(&argc, &argv);
264
#endif
265
266
progname = *argv++;
267
argc--;
268
init_secure();
269
270
#ifdef WIN32
271
if (getenv("HOME") == NULL)
272
{
273
/*
274
* If there is no HOME environment variable,
275
* try the concatenation of HOMEDRIVE + HOMEPATH.
276
*/
277
char *drive = getenv("HOMEDRIVE");
278
char *path = getenv("HOMEPATH");
279
if (drive != NULL && path != NULL)
280
{
281
char *env = (char *) ecalloc(strlen(drive) +
282
strlen(path) + 6, sizeof(char));
283
strcpy(env, "HOME=");
284
strcat(env, drive);
285
strcat(env, path);
286
putenv(env);
287
}
288
}
289
/* on failure, consoleTitle is already a valid empty string */
290
GetConsoleTitleW(consoleTitle, countof(consoleTitle));
291
#endif /* WIN32 */
292
293
/*
294
* Process command line arguments and LESS environment arguments.
295
* Command line arguments override environment arguments.
296
*/
297
is_tty = isatty(1);
298
init_mark();
299
init_cmds();
300
init_poll();
301
init_charset();
302
init_line();
303
init_cmdhist();
304
init_option();
305
init_search();
306
307
/*
308
* If the name of the executable program is "more",
309
* act like LESS_IS_MORE is set.
310
*/
311
if (strcmp(last_component(progname), "more") == 0 &&
312
isnullenv(lgetenv("LESS_IS_MORE"))) {
313
less_is_more = 1;
314
scan_option("-fG", FALSE);
315
}
316
317
init_prompt();
318
319
init_unsupport();
320
s = lgetenv(less_is_more ? "MORE" : "LESS");
321
if (s != NULL)
322
scan_option(s, TRUE);
323
324
#define isoptstring(s) less_is_more ? (((s)[0] == '-') && (s)[1] != '\0') : \
325
(((s)[0] == '-' || (s)[0] == '+') && (s)[1] != '\0')
326
xbuf_init(&xfiles);
327
posixly_correct = (lgetenv("POSIXLY_CORRECT") != NULL);
328
for (i = 0; i < argc; i++)
329
{
330
if (strcmp(argv[i], "--") == 0)
331
end_opts = TRUE;
332
else if (!end_opts && (isoptstring(argv[i]) || isoptpending()))
333
scan_option(argv[i], FALSE);
334
else
335
{
336
if (posixly_correct)
337
end_opts = TRUE;
338
xbuf_add_data(&xfiles, &i, sizeof(i));
339
}
340
}
341
#undef isoptstring
342
343
if (isoptpending())
344
{
345
/*
346
* Last command line option was a flag requiring a
347
* following string, but there was no following string.
348
*/
349
nopendopt();
350
quit(QUIT_OK);
351
}
352
353
if (less_is_more)
354
no_init = TRUE;
355
356
get_term();
357
expand_cmd_tables();
358
359
#if EDITOR
360
editor = lgetenv("VISUAL");
361
if (isnullenv(editor))
362
{
363
editor = lgetenv("EDITOR");
364
if (isnullenv(editor))
365
editor = EDIT_PGM;
366
}
367
editproto = lgetenv("LESSEDIT");
368
if (isnullenv(editproto))
369
editproto = "%E ?lm+%lm. %g";
370
#endif
371
372
/*
373
* Call get_ifile with all the command line filenames
374
* to "register" them with the ifile system.
375
*/
376
ifile = NULL_IFILE;
377
if (dohelp)
378
ifile = get_ifile(FAKE_HELPFILE, ifile);
379
files = (constant int *) xfiles.data;
380
num_files = xfiles.end / sizeof(int);
381
for (f = 0; f < num_files; f++)
382
{
383
#if (MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC)
384
/*
385
* Because the "shell" doesn't expand filename patterns,
386
* treat each argument as a filename pattern rather than
387
* a single filename.
388
* Expand the pattern and iterate over the expanded list.
389
*/
390
struct textlist tlist;
391
constant char *filename;
392
char *gfilename;
393
char *qfilename;
394
395
gfilename = lglob(argv[files[f]]);
396
init_textlist(&tlist, gfilename);
397
filename = NULL;
398
while ((filename = forw_textlist(&tlist, filename)) != NULL)
399
{
400
qfilename = shell_unquote(filename);
401
(void) get_ifile(qfilename, ifile);
402
free(qfilename);
403
ifile = prev_ifile(NULL_IFILE);
404
}
405
free(gfilename);
406
#else
407
(void) get_ifile(argv[files[f]], ifile);
408
ifile = prev_ifile(NULL_IFILE);
409
#endif
410
}
411
xbuf_deinit(&xfiles);
412
413
/*
414
* Set up terminal, etc.
415
*/
416
if (!is_tty)
417
{
418
/*
419
* Output is not a tty.
420
* Just copy the input file(s) to output.
421
*/
422
set_output(1, TRUE); /* write to stdout */
423
SET_BINARY(1);
424
if (edit_first() == 0)
425
{
426
do {
427
cat_file();
428
} while (edit_next(1) == 0);
429
}
430
quit(QUIT_OK);
431
}
432
433
if (missing_cap && !know_dumb && !less_is_more)
434
error("WARNING: terminal is not fully functional", NULL_PARG);
435
open_getchr();
436
raw_mode(1);
437
init_signals(1);
438
#if HAVE_TIME
439
less_start_time = get_time();
440
#endif
441
442
/*
443
* Select the first file to examine.
444
*/
445
#if TAGS
446
if (tagoption != NULL || strcmp(tags, "-") == 0)
447
{
448
/*
449
* A -t option was given.
450
* Verify that no filenames were also given.
451
* Edit the file selected by the "tags" search,
452
* and search for the proper line in the file.
453
*/
454
if (nifile() > 0)
455
{
456
error("No filenames allowed with -t option", NULL_PARG);
457
quit(QUIT_ERROR);
458
}
459
findtag(tagoption);
460
if (edit_tagfile()) /* Edit file which contains the tag */
461
quit(QUIT_ERROR);
462
/*
463
* Search for the line which contains the tag.
464
* Set up initial_scrpos so we display that line.
465
*/
466
initial_scrpos.pos = tagsearch();
467
if (initial_scrpos.pos == NULL_POSITION)
468
quit(QUIT_ERROR);
469
initial_scrpos.ln = jump_sline;
470
} else
471
#endif
472
{
473
if (edit_first())
474
quit(QUIT_ERROR);
475
/*
476
* See if file fits on one screen to decide whether
477
* to send terminal init. But don't need this
478
* if -X (no_init) overrides this (see term_init()).
479
*/
480
if (quit_if_one_screen)
481
{
482
if (nifile() > 1) /* If more than one file, -F cannot be used */
483
quit_if_one_screen = FALSE;
484
else if (!no_init)
485
one_screen = get_one_screen();
486
}
487
}
488
if (init_header != NULL)
489
{
490
opt_header(TOGGLE, init_header);
491
free(init_header);
492
init_header = NULL;
493
}
494
495
if (errmsgs > 0)
496
{
497
/*
498
* We displayed some messages on error output
499
* (file descriptor 2; see flush()).
500
* Before erasing the screen contents, wait for a keystroke.
501
*/
502
less_printf("Press RETURN to continue ", NULL_PARG);
503
get_return();
504
putchr('\n');
505
}
506
set_output(1, FALSE);
507
#if MSDOS_COMPILER
508
term_init();
509
#endif
510
commands();
511
quit(QUIT_OK);
512
/*NOTREACHED*/
513
return (0);
514
}
515
516
/*
517
* Copy a string to a "safe" place
518
* (that is, to a buffer allocated by calloc).
519
*/
520
public char * saven(constant char *s, size_t n)
521
{
522
char *p = (char *) ecalloc(n+1, sizeof(char));
523
strncpy(p, s, n);
524
p[n] = '\0';
525
return (p);
526
}
527
528
public char * save(constant char *s)
529
{
530
return saven(s, strlen(s));
531
}
532
533
public void out_of_memory(void)
534
{
535
error("Cannot allocate memory", NULL_PARG);
536
quit(QUIT_ERROR);
537
}
538
539
/*
540
* Allocate memory.
541
* Like calloc(), but never returns an error (NULL).
542
*/
543
public void * ecalloc(size_t count, size_t size)
544
{
545
void * p;
546
547
p = (void *) calloc(count, size);
548
if (p == NULL)
549
out_of_memory();
550
return p;
551
}
552
553
/*
554
* Skip leading spaces in a string.
555
*/
556
public char * skipsp(char *s)
557
{
558
while (*s == ' ' || *s == '\t')
559
s++;
560
return (s);
561
}
562
563
/* {{ There must be a better way. }} */
564
public constant char * skipspc(constant char *s)
565
{
566
while (*s == ' ' || *s == '\t')
567
s++;
568
return (s);
569
}
570
571
/*
572
* See how many characters of two strings are identical.
573
* If uppercase is true, the first string must begin with an uppercase
574
* character; the remainder of the first string may be either case.
575
*/
576
public size_t sprefix(constant char *ps, constant char *s, int uppercase)
577
{
578
char c;
579
char sc;
580
size_t len = 0;
581
582
for ( ; *s != '\0'; s++, ps++)
583
{
584
c = *ps;
585
if (uppercase)
586
{
587
if (len == 0 && ASCII_IS_LOWER(c))
588
return (0);
589
if (ASCII_IS_UPPER(c))
590
c = ASCII_TO_LOWER(c);
591
}
592
sc = *s;
593
if (len > 0 && ASCII_IS_UPPER(sc))
594
sc = ASCII_TO_LOWER(sc);
595
if (c != sc)
596
break;
597
len++;
598
}
599
return (len);
600
}
601
602
/*
603
* Exit the program.
604
*/
605
public void quit(int status)
606
{
607
static int save_status;
608
609
/*
610
* Put cursor at bottom left corner, clear the line,
611
* reset the terminal modes, and exit.
612
*/
613
if (status < 0)
614
status = save_status;
615
else
616
save_status = status;
617
quitting = TRUE;
618
check_altpipe_error();
619
if (interactive())
620
clear_bot();
621
term_deinit();
622
flush();
623
if (redraw_on_quit && term_addrs)
624
{
625
/*
626
* The last file text displayed might have been on an
627
* alternate screen, which now (since deinit) cannot be seen.
628
* redraw_on_quit tells us to redraw it on the main screen.
629
*/
630
first_time = TRUE; /* Don't print "skipping" or tildes */
631
repaint();
632
flush();
633
}
634
edit((char*)NULL);
635
save_cmdhist();
636
raw_mode(0);
637
#if MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC
638
/*
639
* If we don't close 2, we get some garbage from
640
* 2's buffer when it flushes automatically.
641
* I cannot track this one down RB
642
* The same bug shows up if we use ^C^C to abort.
643
*/
644
close(2);
645
#endif
646
#ifdef WIN32
647
SetConsoleTitleW(consoleTitle);
648
#endif
649
close_getchr();
650
exit(status);
651
}
652
653
/*
654
* Are all the features in the features mask allowed by security?
655
*/
656
public int secure_allow(int features)
657
{
658
return ((secure_allow_features & features) == features);
659
}
660
661