1 /*        $NetBSD: main.c,v 1.5 2023/10/06 05:49:49 simonb Exp $      */
2 
3 /*
4  * Copyright (C) 1984-2023  Mark Nudelman
5  *
6  * You may distribute under the terms of either the GNU General Public
7  * License or the Less License, as specified in the README file.
8  *
9  * For more information, see the README file.
10  */
11 
12 
13 /*
14  * Entry point, initialization, miscellaneous routines.
15  */
16 
17 #include "less.h"
18 #if MSDOS_COMPILER==WIN32C
19 #define WIN32_LEAN_AND_MEAN
20 #include <windows.h>
21 #endif
22 
23 public char *   every_first_cmd = NULL;
24 public int      new_file;
25 public int      is_tty;
26 public IFILE    curr_ifile = NULL_IFILE;
27 public IFILE    old_ifile = NULL_IFILE;
28 public struct scrpos initial_scrpos;
29 public POSITION start_attnpos = NULL_POSITION;
30 public POSITION end_attnpos = NULL_POSITION;
31 public int      wscroll;
32 public char *   progname;
33 public int      quitting;
34 public int      secure;
35 public int      dohelp;
36 public int      more_mode = 0;
37 
38 #if LOGFILE
39 public int      logfile = -1;
40 public int      force_logfile = FALSE;
41 public char *   namelogfile = NULL;
42 #endif
43 
44 #if EDITOR
45 public char *   editor;
46 public char *   editproto;
47 #endif
48 
49 #if TAGS
50 extern char *   tags;
51 extern char *   tagoption;
52 extern int      jump_sline;
53 #endif
54 
55 #ifdef WIN32
56 static char consoleTitle[256];
57 #endif
58 
59 public int      one_screen;
60 extern int      less_is_more;
61 extern int      missing_cap;
62 extern int      know_dumb;
63 extern int      pr_type;
64 extern int      quit_if_one_screen;
65 extern int      no_init;
66 extern int      errmsgs;
67 extern int      redraw_on_quit;
68 extern int      term_init_done;
69 extern int      first_time;
70 
71 /*
72  * Entry point.
73  */
main(int argc,char * argv[])74 int main(int argc, char *argv[])
75 {
76           IFILE ifile;
77           char *s;
78 
79 #ifdef __EMX__
80           _response(&argc, &argv);
81           _wildcard(&argc, &argv);
82 #endif
83 
84           progname = *argv++;
85           argc--;
86 
87 #if SECURE
88           secure = 1;
89 #else
90           secure = 0;
91           s = lgetenv("LESSSECURE");
92           if (!isnullenv(s))
93                     secure = 1;
94 #endif
95 
96 #ifdef WIN32
97           if (getenv("HOME") == NULL)
98           {
99                     /*
100                      * If there is no HOME environment variable,
101                      * try the concatenation of HOMEDRIVE + HOMEPATH.
102                      */
103                     char *drive = getenv("HOMEDRIVE");
104                     char *path  = getenv("HOMEPATH");
105                     if (drive != NULL && path != NULL)
106                     {
107                               char *env = (char *) ecalloc(strlen(drive) +
108                                                   strlen(path) + 6, sizeof(char));
109                               strcpy(env, "HOME=");
110                               strcat(env, drive);
111                               strcat(env, path);
112                               putenv(env);
113                     }
114           }
115           GetConsoleTitle(consoleTitle, sizeof(consoleTitle)/sizeof(char));
116 #endif /* WIN32 */
117 
118           /*
119            * Process command line arguments and LESS environment arguments.
120            * Command line arguments override environment arguments.
121            */
122           if (strcmp(getprogname(), "more") == 0)
123                     more_mode = 1;
124 
125           is_tty = isatty(1);
126           init_mark();
127           init_cmds();
128           init_poll();
129           get_term();
130           init_charset();
131           init_line();
132           init_cmdhist();
133           init_option();
134           init_search();
135 
136           /*
137            * If the name of the executable program is "more",
138            * act like LESS_IS_MORE is set.
139            */
140           s = last_component(progname);
141           if (strcmp(s, "more") == 0)
142                     less_is_more = 1;
143 
144           init_prompt();
145 
146           s = lgetenv(less_is_more ? "MORE" : "LESS");
147           if (s != NULL)
148                     scan_option(s);
149 
150 #define isoptstring(s)  (((s)[0] == '-' || (s)[0] == '+') && (s)[1] != '\0')
151           while (argc > 0 && (isoptstring(*argv) || isoptpending()))
152           {
153                     s = *argv++;
154                     argc--;
155                     if (strcmp(s, "--") == 0)
156                               break;
157                     scan_option(s);
158           }
159 #undef isoptstring
160 
161           if (isoptpending())
162           {
163                     /*
164                      * Last command line option was a flag requiring a
165                      * following string, but there was no following string.
166                      */
167                     nopendopt();
168                     quit(QUIT_OK);
169           }
170 
171           expand_cmd_tables();
172 
173 #if EDITOR
174           editor = lgetenv("VISUAL");
175           if (editor == NULL || *editor == '\0')
176           {
177                     editor = lgetenv("EDITOR");
178                     if (isnullenv(editor))
179                               editor = EDIT_PGM;
180           }
181           editproto = lgetenv("LESSEDIT");
182           if (isnullenv(editproto))
183                     editproto = "%E ?lm+%lm. %g";
184 #endif
185 
186           /*
187            * Call get_ifile with all the command line filenames
188            * to "register" them with the ifile system.
189            */
190           ifile = NULL_IFILE;
191           if (dohelp)
192                     ifile = get_ifile(FAKE_HELPFILE, ifile);
193           while (argc-- > 0)
194           {
195 #if (MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC)
196                     /*
197                      * Because the "shell" doesn't expand filename patterns,
198                      * treat each argument as a filename pattern rather than
199                      * a single filename.
200                      * Expand the pattern and iterate over the expanded list.
201                      */
202                     struct textlist tlist;
203                     char *filename;
204                     char *gfilename;
205                     char *qfilename;
206 
207                     gfilename = lglob(*argv++);
208                     init_textlist(&tlist, gfilename);
209                     filename = NULL;
210                     while ((filename = forw_textlist(&tlist, filename)) != NULL)
211                     {
212                               qfilename = shell_unquote(filename);
213                               (void) get_ifile(qfilename, ifile);
214                               free(qfilename);
215                               ifile = prev_ifile(NULL_IFILE);
216                     }
217                     free(gfilename);
218 #else
219                     (void) get_ifile(*argv++, ifile);
220                     ifile = prev_ifile(NULL_IFILE);
221 #endif
222           }
223           /*
224            * Set up terminal, etc.
225            */
226           if (!is_tty)
227           {
228                     /*
229                      * Output is not a tty.
230                      * Just copy the input file(s) to output.
231                      */
232                     set_output(1); /* write to stdout */
233                     SET_BINARY(1);
234                     if (edit_first() == 0)
235                     {
236                               do {
237                                         cat_file();
238                               } while (edit_next(1) == 0);
239                     }
240                     quit(QUIT_OK);
241           }
242 
243           if (missing_cap && !know_dumb && !more_mode)
244                     error("WARNING: terminal is not fully functional", NULL_PARG);
245           open_getchr();
246           raw_mode(1);
247           init_signals(1);
248 
249           /*
250            * Select the first file to examine.
251            */
252 #if TAGS
253           if (tagoption != NULL || strcmp(tags, "-") == 0)
254           {
255                     /*
256                      * A -t option was given.
257                      * Verify that no filenames were also given.
258                      * Edit the file selected by the "tags" search,
259                      * and search for the proper line in the file.
260                      */
261                     if (nifile() > 0)
262                     {
263                               error("No filenames allowed with -t option", NULL_PARG);
264                               quit(QUIT_ERROR);
265                     }
266                     findtag(tagoption);
267                     if (edit_tagfile())  /* Edit file which contains the tag */
268                               quit(QUIT_ERROR);
269                     /*
270                      * Search for the line which contains the tag.
271                      * Set up initial_scrpos so we display that line.
272                      */
273                     initial_scrpos.pos = tagsearch();
274                     if (initial_scrpos.pos == NULL_POSITION)
275                               quit(QUIT_ERROR);
276                     initial_scrpos.ln = jump_sline;
277           } else
278 #endif
279           {
280                     if (edit_first())
281                               quit(QUIT_ERROR);
282                     /*
283                      * See if file fits on one screen to decide whether
284                      * to send terminal init. But don't need this
285                      * if -X (no_init) overrides this (see init()).
286                      */
287                     if (quit_if_one_screen)
288                     {
289                               if (nifile() > 1) /* If more than one file, -F cannot be used */
290                                         quit_if_one_screen = FALSE;
291                               else if (!no_init)
292                                         one_screen = get_one_screen();
293                     }
294           }
295 
296           if (errmsgs > 0)
297           {
298                     /*
299                      * We displayed some messages on error output
300                      * (file descriptor 2; see flush()).
301                      * Before erasing the screen contents, wait for a keystroke.
302                      */
303                     less_printf("Press RETURN to continue ", NULL_PARG);
304                     get_return();
305                     putchr('\n');
306           }
307           set_output(1);
308           init();
309           commands();
310           quit(QUIT_OK);
311           /*NOTREACHED*/
312           return (0);
313 }
314 
315 /*
316  * Copy a string to a "safe" place
317  * (that is, to a buffer allocated by calloc).
318  */
save(constant char * s)319 public char * save(constant char *s)
320 {
321           char *p;
322 
323           p = (char *) ecalloc(strlen(s)+1, sizeof(char));
324           strcpy(p, s);
325           return (p);
326 }
327 
out_of_memory(void)328 public void out_of_memory(void)
329 {
330           error("Cannot allocate memory", NULL_PARG);
331           quit(QUIT_ERROR);
332 }
333 
334 /*
335  * Allocate memory.
336  * Like calloc(), but never returns an error (NULL).
337  */
ecalloc(int count,unsigned int size)338 public void * ecalloc(int count, unsigned int size)
339 {
340           void * p;
341 
342           p = (void *) calloc(count, size);
343           if (p == NULL)
344                     out_of_memory();
345           return p;
346 }
347 
348 /*
349  * Skip leading spaces in a string.
350  */
skipsp(char * s)351 public char * skipsp(char *s)
352 {
353           while (*s == ' ' || *s == '\t')
354                     s++;
355           return (s);
356 }
357 
358 /*
359  * See how many characters of two strings are identical.
360  * If uppercase is true, the first string must begin with an uppercase
361  * character; the remainder of the first string may be either case.
362  */
sprefix(char * ps,char * s,int uppercase)363 public int sprefix(char *ps, char *s, int uppercase)
364 {
365           int c;
366           int sc;
367           int len = 0;
368 
369           for ( ;  *s != '\0';  s++, ps++)
370           {
371                     c = *ps;
372                     if (uppercase)
373                     {
374                               if (len == 0 && ASCII_IS_LOWER(c))
375                                         return (-1);
376                               if (ASCII_IS_UPPER(c))
377                                         c = ASCII_TO_LOWER(c);
378                     }
379                     sc = *s;
380                     if (len > 0 && ASCII_IS_UPPER(sc))
381                               sc = ASCII_TO_LOWER(sc);
382                     if (c != sc)
383                               break;
384                     len++;
385           }
386           return (len);
387 }
388 
389 /*
390  * Exit the program.
391  */
quit(int status)392 public void quit(int status)
393 {
394           static int save_status;
395 
396           /*
397            * Put cursor at bottom left corner, clear the line,
398            * reset the terminal modes, and exit.
399            */
400           if (status < 0)
401                     status = save_status;
402           else
403                     save_status = status;
404           quitting = 1;
405           check_altpipe_error();
406           if (interactive())
407                     clear_bot();
408           deinit();
409           flush();
410           if (redraw_on_quit && term_init_done)
411           {
412                     /*
413                      * The last file text displayed might have been on an
414                      * alternate screen, which now (since deinit) cannot be seen.
415                      * redraw_on_quit tells us to redraw it on the main screen.
416                      */
417                     first_time = 1; /* Don't print "skipping" or tildes */
418                     repaint();
419                     flush();
420           }
421           edit((char*)NULL);
422           save_cmdhist();
423           raw_mode(0);
424 #if MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC
425           /*
426            * If we don't close 2, we get some garbage from
427            * 2's buffer when it flushes automatically.
428            * I cannot track this one down  RB
429            * The same bug shows up if we use ^C^C to abort.
430            */
431           close(2);
432 #endif
433 #ifdef WIN32
434           SetConsoleTitle(consoleTitle);
435 #endif
436           close_getchr();
437           exit(status);
438 }
439