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