2 * Copyright (C) 1984-2024 Mark Nudelman
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.
7 * For more information, see the README file.
12 * Entry point, initialization, miscellaneous routines.
16 #if MSDOS_COMPILER==WIN32C
17 #define WIN32_LEAN_AND_MEAN
20 #if defined(MINGW) || defined(_MSC_VER)
25 public unsigned less_acp
= CP_ACP
;
28 public char * every_first_cmd
= NULL
;
29 public lbool new_file
;
31 public IFILE curr_ifile
= NULL_IFILE
;
32 public IFILE old_ifile
= NULL_IFILE
;
33 public struct scrpos initial_scrpos
;
34 public POSITION start_attnpos
= NULL_POSITION
;
35 public POSITION end_attnpos
= NULL_POSITION
;
37 public constant
char *progname
;
40 static int secure_allow_features
;
43 public int logfile
= -1;
44 public lbool force_logfile
= FALSE
;
45 public char * namelogfile
= NULL
;
49 public constant
char * editor
;
50 public constant
char * editproto
;
55 extern char * tagoption
;
56 extern int jump_sline
;
60 static wchar_t consoleTitle
[256];
63 public int one_screen
;
64 extern int less_is_more
;
65 extern int missing_cap
;
67 extern int quit_if_one_screen
;
70 extern int redraw_on_quit
;
71 extern int term_init_done
;
72 extern int first_time
;
74 #if MSDOS_COMPILER==WIN32C && (defined(MINGW) || defined(_MSC_VER))
75 /* malloc'ed 0-terminated utf8 of 0-terminated wide ws, or null on errors */
76 static char *utf8_from_wide(constant
wchar_t *ws
)
79 int n
= WideCharToMultiByte(CP_UTF8
, 0, ws
, -1, NULL
, 0, NULL
, NULL
);
82 u8
= ecalloc(n
, sizeof(char));
83 WideCharToMultiByte(CP_UTF8
, 0, ws
, -1, u8
, n
, NULL
, NULL
);
89 * similar to using UTF8 manifest to make the ANSI APIs UTF8, but dynamically
90 * with setlocale. unlike the manifest, argv and environ are already ACP, so
91 * make them UTF8. Additionally, this affects only the libc/crt API, and so
92 * e.g. fopen filename becomes UTF-8, but CreateFileA filename remains CP_ACP.
93 * CP_ACP remains the original codepage - use the dynamic less_acp instead.
94 * effective on win 10 1803 or later when compiled with ucrt, else no-op.
96 static void try_utf8_locale(int *pargc
, constant
char ***pargv
)
98 char *locale_orig
= strdup(setlocale(LC_ALL
, NULL
));
99 wchar_t **wargv
= NULL
, *wenv
, *wp
;
100 constant
char **u8argv
;
104 if (!setlocale(LC_ALL
, ".UTF8"))
105 goto cleanup
; /* not win10 1803+ or not ucrt */
108 * wargv is before glob expansion. some ucrt builds may expand globs
109 * before main is entered, so n may be smaller than the original argc.
110 * that's ok, because later code at main expands globs anyway.
112 wargv
= CommandLineToArgvW(GetCommandLineW(), &n
);
116 u8argv
= (constant
char **) ecalloc(n
+ 1, sizeof(char *));
117 for (i
= 0; i
< n
; ++i
)
119 if (!(u8argv
[i
] = utf8_from_wide(wargv
[i
])))
126 *pargv
= u8argv
; /* leaked on exit */
128 /* convert wide env to utf8 where we can, but don't abort on errors */
129 if ((wenv
= GetEnvironmentStringsW()))
131 for (wp
= wenv
; *wp
; wp
+= wcslen(wp
) + 1)
133 if ((u8e
= utf8_from_wide(wp
)))
135 free(u8e
); /* windows putenv makes a copy */
137 FreeEnvironmentStringsW(wenv
);
143 error("WARNING: cannot use unicode arguments", NULL_PARG
);
144 setlocale(LC_ALL
, locale_orig
);
152 static int security_feature_error(constant
char *type
, size_t len
, constant
char *name
)
155 size_t msglen
= len
+ strlen(type
) + 64;
156 char *msg
= ecalloc(msglen
, sizeof(char));
157 SNPRINTF3(msg
, msglen
, "LESSSECURE_ALLOW: %s feature name \"%.*s\"", type
, (int) len
, name
);
165 * Return the SF_xxx value of a secure feature given the name of the feature.
167 static int security_feature(constant
char *name
, size_t len
)
169 struct secure_feature
{ constant
char *name
; int sf_value
; };
170 static struct secure_feature features
[] = {
172 { "examine", SF_EXAMINE
},
174 { "history", SF_HISTORY
},
175 { "lesskey", SF_LESSKEY
},
176 { "lessopen", SF_LESSOPEN
},
177 { "logfile", SF_LOGFILE
},
178 { "osc8", SF_OSC8_OPEN
},
180 { "shell", SF_SHELL
},
187 for (i
= 0; i
< countof(features
); i
++)
189 if (strncmp(features
[i
].name
, name
, len
) == 0)
191 if (match
>= 0) /* name is ambiguous */
192 return security_feature_error("ambiguous", len
, name
);
197 return security_feature_error("invalid", len
, name
);
198 return features
[match
].sf_value
;
202 * Set the secure_allow_features bitmask, which controls
203 * whether certain secure features are allowed.
205 static void init_secure(void)
208 secure_allow_features
= 0;
210 constant
char *str
= lgetenv("LESSSECURE");
212 secure_allow_features
= ~0; /* allow everything */
214 secure_allow_features
= 0; /* allow nothing */
216 str
= lgetenv("LESSSECURE_ALLOW");
222 while (*str
== ' ' || *str
== ',') ++str
; /* skip leading spaces/commas */
223 if (*str
== '\0') break;
224 estr
= strchr(str
, ',');
225 if (estr
== NULL
) estr
= str
+ strlen(str
);
226 while (estr
> str
&& estr
[-1] == ' ') --estr
; /* trim trailing spaces */
227 secure_allow_features
|= security_feature(str
, ptr_diff(estr
, str
));
237 int main(int argc
, constant
char *argv
[])
242 #if MSDOS_COMPILER==WIN32C && (defined(MINGW) || defined(_MSC_VER))
243 if (GetACP() != CP_UTF8
) /* not using a UTF-8 manifest */
244 try_utf8_locale(&argc
, &argv
);
248 _response(&argc
, &argv
);
249 _wildcard(&argc
, &argv
);
257 if (getenv("HOME") == NULL
)
260 * If there is no HOME environment variable,
261 * try the concatenation of HOMEDRIVE + HOMEPATH.
263 char *drive
= getenv("HOMEDRIVE");
264 char *path
= getenv("HOMEPATH");
265 if (drive
!= NULL
&& path
!= NULL
)
267 char *env
= (char *) ecalloc(strlen(drive
) +
268 strlen(path
) + 6, sizeof(char));
269 strcpy(env
, "HOME=");
275 /* on failure, consoleTitle is already a valid empty string */
276 GetConsoleTitleW(consoleTitle
, countof(consoleTitle
));
280 * Process command line arguments and LESS environment arguments.
281 * Command line arguments override environment arguments.
294 * If the name of the executable program is "more",
295 * act like LESS_IS_MORE is set.
297 if (strcmp(last_component(progname
), "more") == 0)
303 s
= lgetenv(less_is_more
? "MORE" : "LESS");
307 #define isoptstring(s) (((s)[0] == '-' || (s)[0] == '+') && (s)[1] != '\0')
308 while (argc
> 0 && (isoptstring(*argv
) || isoptpending()))
312 if (strcmp(s
, "--") == 0)
321 * Last command line option was a flag requiring a
322 * following string, but there was no following string.
332 editor
= lgetenv("VISUAL");
333 if (editor
== NULL
|| *editor
== '\0')
335 editor
= lgetenv("EDITOR");
336 if (isnullenv(editor
))
339 editproto
= lgetenv("LESSEDIT");
340 if (isnullenv(editproto
))
341 editproto
= "%E ?lm+%lm. %g";
345 * Call get_ifile with all the command line filenames
346 * to "register" them with the ifile system.
350 ifile
= get_ifile(FAKE_HELPFILE
, ifile
);
353 #if (MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC)
355 * Because the "shell" doesn't expand filename patterns,
356 * treat each argument as a filename pattern rather than
358 * Expand the pattern and iterate over the expanded list.
360 struct textlist tlist
;
361 constant
char *filename
;
365 gfilename
= lglob(*argv
++);
366 init_textlist(&tlist
, gfilename
);
368 while ((filename
= forw_textlist(&tlist
, filename
)) != NULL
)
370 qfilename
= shell_unquote(filename
);
371 (void) get_ifile(qfilename
, ifile
);
373 ifile
= prev_ifile(NULL_IFILE
);
377 (void) get_ifile(*argv
++, ifile
);
378 ifile
= prev_ifile(NULL_IFILE
);
382 * Set up terminal, etc.
387 * Output is not a tty.
388 * Just copy the input file(s) to output.
390 set_output(1); /* write to stdout */
392 if (edit_first() == 0)
396 } while (edit_next(1) == 0);
401 if (missing_cap
&& !know_dumb
)
402 error("WARNING: terminal is not fully functional", NULL_PARG
);
408 * Select the first file to examine.
411 if (tagoption
!= NULL
|| strcmp(tags
, "-") == 0)
414 * A -t option was given.
415 * Verify that no filenames were also given.
416 * Edit the file selected by the "tags" search,
417 * and search for the proper line in the file.
421 error("No filenames allowed with -t option", NULL_PARG
);
425 if (edit_tagfile()) /* Edit file which contains the tag */
428 * Search for the line which contains the tag.
429 * Set up initial_scrpos so we display that line.
431 initial_scrpos
.pos
= tagsearch();
432 if (initial_scrpos
.pos
== NULL_POSITION
)
434 initial_scrpos
.ln
= jump_sline
;
441 * See if file fits on one screen to decide whether
442 * to send terminal init. But don't need this
443 * if -X (no_init) overrides this (see init()).
445 if (quit_if_one_screen
)
447 if (nifile() > 1) /* If more than one file, -F cannot be used */
448 quit_if_one_screen
= FALSE
;
450 one_screen
= get_one_screen();
457 * We displayed some messages on error output
458 * (file descriptor 2; see flush()).
459 * Before erasing the screen contents, wait for a keystroke.
461 less_printf("Press RETURN to continue ", NULL_PARG
);
474 * Copy a string to a "safe" place
475 * (that is, to a buffer allocated by calloc).
477 public char * saven(constant
char *s
, size_t n
)
479 char *p
= (char *) ecalloc(n
+1, sizeof(char));
485 public char * save(constant
char *s
)
487 return saven(s
, strlen(s
));
490 public void out_of_memory(void)
492 error("Cannot allocate memory", NULL_PARG
);
498 * Like calloc(), but never returns an error (NULL).
500 public void * ecalloc(size_t count
, size_t size
)
504 p
= (void *) calloc(count
, size
);
511 * Skip leading spaces in a string.
513 public char * skipsp(char *s
)
515 while (*s
== ' ' || *s
== '\t')
520 /* {{ There must be a better way. }} */
521 public constant
char * skipspc(constant
char *s
)
523 while (*s
== ' ' || *s
== '\t')
529 * See how many characters of two strings are identical.
530 * If uppercase is true, the first string must begin with an uppercase
531 * character; the remainder of the first string may be either case.
533 public size_t sprefix(constant
char *ps
, constant
char *s
, int uppercase
)
539 for ( ; *s
!= '\0'; s
++, ps
++)
544 if (len
== 0 && ASCII_IS_LOWER(c
))
546 if (ASCII_IS_UPPER(c
))
547 c
= ASCII_TO_LOWER(c
);
550 if (len
> 0 && ASCII_IS_UPPER(sc
))
551 sc
= ASCII_TO_LOWER(sc
);
562 public void quit(int status
)
564 static int save_status
;
567 * Put cursor at bottom left corner, clear the line,
568 * reset the terminal modes, and exit.
571 status
= save_status
;
573 save_status
= status
;
575 check_altpipe_error();
580 if (redraw_on_quit
&& term_init_done
)
583 * The last file text displayed might have been on an
584 * alternate screen, which now (since deinit) cannot be seen.
585 * redraw_on_quit tells us to redraw it on the main screen.
587 first_time
= 1; /* Don't print "skipping" or tildes */
594 #if MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC
596 * If we don't close 2, we get some garbage from
597 * 2's buffer when it flushes automatically.
598 * I cannot track this one down RB
599 * The same bug shows up if we use ^C^C to abort.
604 SetConsoleTitleW(consoleTitle
);
611 * Are all the features in the features mask allowed by security?
613 public int secure_allow(int features
)
615 return ((secure_allow_features
& features
) == features
);