1 /* 2 * Copyright (C) 1984-2026 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