GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/less/less/../edit.c Lines: 111 253 43.9 %
Date: 2017-11-07 Branches: 47 154 30.5 %

Line Branch Exec Source
1
/*
2
 * Copyright (C) 1984-2012  Mark Nudelman
3
 * Modified for use with illumos by Garrett D'Amore.
4
 * Copyright 2014 Garrett D'Amore <garrett@damore.org>
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
#include <sys/stat.h>
13
14
#include "less.h"
15
16
static int fd0 = 0;
17
18
extern int new_file;
19
extern int errmsgs;
20
extern char *every_first_cmd;
21
extern int any_display;
22
extern int force_open;
23
extern int is_tty;
24
extern volatile sig_atomic_t sigs;
25
extern IFILE curr_ifile;
26
extern IFILE old_ifile;
27
extern struct scrpos initial_scrpos;
28
extern void *ml_examine;
29
extern char openquote;
30
extern char closequote;
31
extern int less_is_more;
32
extern int logfile;
33
extern int force_logfile;
34
extern char *namelogfile;
35
36
dev_t curr_dev;
37
ino_t curr_ino;
38
39
char *curr_altfilename = NULL;
40
static void *curr_altpipe;
41
42
43
/*
44
 * Textlist functions deal with a list of words separated by spaces.
45
 * init_textlist sets up a textlist structure.
46
 * forw_textlist uses that structure to iterate thru the list of
47
 * words, returning each one as a standard null-terminated string.
48
 * back_textlist does the same, but runs thru the list backwards.
49
 */
50
void
51
init_textlist(struct textlist *tlist, char *str)
52
{
53
	char *s;
54
	int meta_quoted = 0;
55
	int delim_quoted = 0;
56
	char *esc = get_meta_escape();
57
	int esclen = strlen(esc);
58
59
	tlist->string = skipsp(str);
60
	tlist->endstring = tlist->string + strlen(tlist->string);
61
	for (s = str; s < tlist->endstring; s++) {
62
		if (meta_quoted) {
63
			meta_quoted = 0;
64
		} else if (esclen > 0 && s + esclen < tlist->endstring &&
65
		    strncmp(s, esc, esclen) == 0) {
66
			meta_quoted = 1;
67
			s += esclen - 1;
68
		} else if (delim_quoted) {
69
			if (*s == closequote)
70
				delim_quoted = 0;
71
		} else /* (!delim_quoted) */ {
72
			if (*s == openquote)
73
				delim_quoted = 1;
74
			else if (*s == ' ')
75
				*s = '\0';
76
		}
77
	}
78
}
79
80
char *
81
forw_textlist(struct textlist *tlist, char *prev)
82
{
83
	char *s;
84
85
	/*
86
	 * prev == NULL means return the first word in the list.
87
	 * Otherwise, return the word after "prev".
88
	 */
89
	if (prev == NULL)
90
		s = tlist->string;
91
	else
92
		s = prev + strlen(prev);
93
	if (s >= tlist->endstring)
94
		return (NULL);
95
	while (*s == '\0')
96
		s++;
97
	if (s >= tlist->endstring)
98
		return (NULL);
99
	return (s);
100
}
101
102
char *
103
back_textlist(struct textlist *tlist, char *prev)
104
{
105
	char *s;
106
107
	/*
108
	 * prev == NULL means return the last word in the list.
109
	 * Otherwise, return the word before "prev".
110
	 */
111
	if (prev == NULL)
112
		s = tlist->endstring;
113
	else if (prev <= tlist->string)
114
		return (NULL);
115
	else
116
		s = prev - 1;
117
	while (*s == '\0')
118
		s--;
119
	if (s <= tlist->string)
120
		return (NULL);
121
	while (s[-1] != '\0' && s > tlist->string)
122
		s--;
123
	return (s);
124
}
125
126
/*
127
 * Close the current input file.
128
 */
129
static void
130
close_file(void)
131
{
132
74
	struct scrpos scrpos;
133
134
37
	if (curr_ifile == NULL)
135
		return;
136
137
	/*
138
	 * Save the current position so that we can return to
139
	 * the same position if we edit this file again.
140
	 */
141
37
	get_scrpos(&scrpos);
142
37
	if (scrpos.pos != -1) {
143
37
		store_pos(curr_ifile, &scrpos);
144
37
		lastmark();
145
37
	}
146
	/*
147
	 * Close the file descriptor, unless it is a pipe.
148
	 */
149
37
	ch_close();
150
	/*
151
	 * If we opened a file using an alternate name,
152
	 * do special stuff to close it.
153
	 */
154
37
	if (curr_altfilename != NULL) {
155
		close_altfile(curr_altfilename, get_filename(curr_ifile),
156
		    curr_altpipe);
157
		free(curr_altfilename);
158
		curr_altfilename = NULL;
159
	}
160
37
	curr_ifile = NULL;
161
37
	curr_ino = curr_dev = 0;
162
74
}
163
164
/*
165
 * Edit a new file (given its name).
166
 * Filename == "-" means standard input.
167
 * Filename == NULL means just close the current file.
168
 */
169
int
170
edit(char *filename)
171
{
172
102
	if (filename == NULL)
173
39
		return (edit_ifile(NULL));
174
12
	return (edit_ifile(get_ifile(filename, curr_ifile)));
175
51
}
176
177
/*
178
 * Edit a new file (given its IFILE).
179
 * ifile == NULL means just close the current file.
180
 */
181
int
182
edit_ifile(IFILE ifile)
183
{
184
160
	int f;
185
	int answer;
186
	int no_display;
187
	int chflags;
188
	char *filename;
189
	char *open_filename;
190
	char *qopen_filename;
191
	char *alt_filename;
192
80
	void *alt_pipe;
193
	IFILE was_curr_ifile;
194
80
	PARG parg;
195
196
80
	if (ifile == curr_ifile) {
197
		/*
198
		 * Already have the correct file open.
199
		 */
200
4
		return (0);
201
	}
202
203
	/*
204
	 * We must close the currently open file now.
205
	 * This is necessary to make the open_altfile/close_altfile pairs
206
	 * nest properly (or rather to avoid nesting at all).
207
	 * {{ Some stupid implementations of popen() mess up if you do:
208
	 *    fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }}
209
	 */
210
76
	end_logfile();
211
76
	was_curr_ifile = save_curr_ifile();
212
76
	if (curr_ifile != NULL) {
213
37
		chflags = ch_getflags();
214
37
		close_file();
215

37
		if ((chflags & CH_HELPFILE) &&
216
		    held_ifile(was_curr_ifile) <= 1) {
217
			/*
218
			 * Don't keep the help file in the ifile list.
219
			 */
220
			del_ifile(was_curr_ifile);
221
			was_curr_ifile = old_ifile;
222
		}
223
	}
224
225
76
	if (ifile == NULL) {
226
		/*
227
		 * No new file to open.
228
		 * (Don't set old_ifile, because if you call edit_ifile(NULL),
229
		 *  you're supposed to have saved curr_ifile yourself,
230
		 *  and you'll restore it if necessary.)
231
		 */
232
37
		unsave_ifile(was_curr_ifile);
233
37
		return (0);
234
	}
235
236
39
	filename = estrdup(get_filename(ifile));
237
	/*
238
	 * See if LESSOPEN specifies an "alternate" file to open.
239
	 */
240
39
	alt_pipe = NULL;
241
39
	alt_filename = open_altfile(filename, &f, &alt_pipe);
242
39
	open_filename = (alt_filename != NULL) ? alt_filename : filename;
243
39
	qopen_filename = shell_unquote(open_filename);
244
245
	chflags = 0;
246
39
	if (strcmp(open_filename, helpfile()) == 0)
247
		chflags |= CH_HELPFILE;
248
39
	if (alt_pipe != NULL) {
249
		/*
250
		 * The alternate "file" is actually a pipe.
251
		 * f has already been set to the file descriptor of the pipe
252
		 * in the call to open_altfile above.
253
		 * Keep the file descriptor open because it was opened
254
		 * via popen(), and pclose() wants to close it.
255
		 */
256
		chflags |= CH_POPENED;
257
39
	} else if (strcmp(open_filename, "-") == 0) {
258
		/*
259
		 * Use standard input.
260
		 * Keep the file descriptor open because we can't reopen it.
261
		 */
262
12
		f = fd0;
263
12
		chflags |= CH_KEEPOPEN;
264
39
	} else if (strcmp(open_filename, FAKE_EMPTYFILE) == 0) {
265
		f = -1;
266
		chflags |= CH_NODATA;
267
27
	} else if ((parg.p_string = bad_file(open_filename)) != NULL) {
268
		/*
269
		 * It looks like a bad file.  Don't try to open it.
270
		 */
271
2
		error("%s", &parg);
272
2
		free(parg.p_string);
273
err1:
274
2
		if (alt_filename != NULL) {
275
			close_altfile(alt_filename, filename, alt_pipe);
276
			free(alt_filename);
277
		}
278
2
		del_ifile(ifile);
279
2
		free(qopen_filename);
280
2
		free(filename);
281
		/*
282
		 * Re-open the current file.
283
		 */
284
2
		if (was_curr_ifile == ifile) {
285
			/*
286
			 * Whoops.  The "current" ifile is the one we just
287
			 * deleted. Just give up.
288
			 */
289
			quit(QUIT_ERROR);
290
		}
291
2
		reedit_ifile(was_curr_ifile);
292
2
		return (1);
293
25
	} else if ((f = open(qopen_filename, O_RDONLY)) < 0) {
294
		/*
295
		 * Got an error trying to open it.
296
		 */
297
		parg.p_string = errno_message(filename);
298
		error("%s", &parg);
299
		free(parg.p_string);
300
		goto err1;
301
	} else {
302
25
		chflags |= CH_CANSEEK;
303

75
		if (!force_open && !opened(ifile) && bin_file(f)) {
304
			/*
305
			 * Looks like a binary file.
306
			 * Ask user if we should proceed.
307
			 */
308
			parg.p_string = filename;
309
			answer = query("\"%s\" may be a binary file.  "
310
			    "See it anyway? ", &parg);
311
			if (answer != 'y' && answer != 'Y') {
312
				(void) close(f);
313
				goto err1;
314
			}
315
		}
316
	}
317
318
	/*
319
	 * Get the new ifile.
320
	 * Get the saved position for the file.
321
	 */
322
37
	if (was_curr_ifile != NULL) {
323
		old_ifile = was_curr_ifile;
324
		unsave_ifile(was_curr_ifile);
325
	}
326
37
	curr_ifile = ifile;
327
37
	curr_altfilename = alt_filename;
328
37
	curr_altpipe = alt_pipe;
329
37
	set_open(curr_ifile); /* File has been opened */
330
37
	get_pos(curr_ifile, &initial_scrpos);
331
37
	new_file = TRUE;
332
37
	ch_init(f, chflags);
333
334
37
	if (!(chflags & CH_HELPFILE)) {
335
37
		struct stat statbuf;
336
		int r;
337
338
37
		if (namelogfile != NULL && is_tty)
339
			use_logfile(namelogfile);
340
		/* Remember the i-number and device of opened file. */
341
37
		r = stat(qopen_filename, &statbuf);
342
37
		if (r == 0) {
343
25
			curr_ino = statbuf.st_ino;
344
25
			curr_dev = statbuf.st_dev;
345
25
		}
346
37
		if (every_first_cmd != NULL)
347
			ungetsc(every_first_cmd);
348
37
	}
349
37
	free(qopen_filename);
350
37
	no_display = !any_display;
351
37
	flush(0);
352
37
	any_display = TRUE;
353
354
37
	if (is_tty) {
355
		/*
356
		 * Output is to a real tty.
357
		 */
358
359
		/*
360
		 * Indicate there is nothing displayed yet.
361
		 */
362
37
		pos_clear();
363
37
		clr_linenum();
364
37
		clr_hilite();
365
37
		cmd_addhist(ml_examine, filename);
366
37
		if (no_display && errmsgs > 0) {
367
			/*
368
			 * We displayed some messages on error output
369
			 * (file descriptor 2; see error() function).
370
			 * Before erasing the screen contents,
371
			 * display the file name and wait for a keystroke.
372
			 */
373
			parg.p_string = filename;
374
			error("%s", &parg);
375
		}
376
	}
377
37
	free(filename);
378
37
	return (0);
379
80
}
380
381
/*
382
 * Edit a space-separated list of files.
383
 * For each filename in the list, enter it into the ifile list.
384
 * Then edit the first one.
385
 */
386
int
387
edit_list(char *filelist)
388
{
389
	IFILE save_ifile;
390
	char *good_filename;
391
	char *filename;
392
	char *gfilelist;
393
	char *gfilename;
394
	struct textlist tl_files;
395
	struct textlist tl_gfiles;
396
397
	save_ifile = save_curr_ifile();
398
	good_filename = NULL;
399
400
	/*
401
	 * Run thru each filename in the list.
402
	 * Try to glob the filename.
403
	 * If it doesn't expand, just try to open the filename.
404
	 * If it does expand, try to open each name in that list.
405
	 */
406
	init_textlist(&tl_files, filelist);
407
	filename = NULL;
408
	while ((filename = forw_textlist(&tl_files, filename)) != NULL) {
409
		gfilelist = lglob(filename);
410
		init_textlist(&tl_gfiles, gfilelist);
411
		gfilename = NULL;
412
		while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) !=
413
		    NULL) {
414
			if (edit(gfilename) == 0 && good_filename == NULL)
415
				good_filename = get_filename(curr_ifile);
416
		}
417
		free(gfilelist);
418
	}
419
	/*
420
	 * Edit the first valid filename in the list.
421
	 */
422
	if (good_filename == NULL) {
423
		unsave_ifile(save_ifile);
424
		return (1);
425
	}
426
	if (get_ifile(good_filename, curr_ifile) == curr_ifile) {
427
		/*
428
		 * Trying to edit the current file; don't reopen it.
429
		 */
430
		unsave_ifile(save_ifile);
431
		return (0);
432
	}
433
	reedit_ifile(save_ifile);
434
	return (edit(good_filename));
435
}
436
437
/*
438
 * Edit the first file in the command line (ifile) list.
439
 */
440
int
441
edit_first(void)
442
{
443
54
	curr_ifile = NULL;
444
27
	return (edit_next(1));
445
}
446
447
/*
448
 * Edit the last file in the command line (ifile) list.
449
 */
450
int
451
edit_last(void)
452
{
453
	curr_ifile = NULL;
454
	return (edit_prev(1));
455
}
456
457
458
/*
459
 * Edit the n-th next or previous file in the command line (ifile) list.
460
 */
461
static int
462
edit_istep(IFILE h, int n, int dir)
463
{
464
	IFILE next;
465
466
	/*
467
	 * Skip n filenames, then try to edit each filename.
468
	 */
469
87
	for (;;) {
470
171
		next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
471
57
		if (--n < 0) {
472
27
			if (edit_ifile(h) == 0)
473
				break;
474
		}
475
32
		if (next == NULL) {
476
			/*
477
			 * Reached end of the ifile list.
478
			 */
479
5
			return (1);
480
		}
481
27
		if (ABORT_SIGS()) {
482
			/*
483
			 * Interrupt breaks out, if we're in a long
484
			 * list of files that can't be opened.
485
			 */
486
			return (1);
487
		}
488
		h = next;
489
	}
490
	/*
491
	 * Found a file that we can edit.
492
	 */
493
25
	return (0);
494
30
}
495
496
static int
497
edit_inext(IFILE h, int n)
498
{
499
	return (edit_istep(h, n, +1));
500
}
501
502
int
503
edit_next(int n)
504
{
505
60
	return (edit_istep(curr_ifile, n, +1));
506
}
507
508
static int
509
edit_iprev(IFILE h, int n)
510
{
511
	return (edit_istep(h, n, -1));
512
}
513
514
int
515
edit_prev(int n)
516
{
517
	return (edit_istep(curr_ifile, n, -1));
518
}
519
520
/*
521
 * Edit a specific file in the command line (ifile) list.
522
 */
523
int
524
edit_index(int n)
525
{
526
	IFILE h;
527
528
	h = NULL;
529
	do {
530
		if ((h = next_ifile(h)) == NULL) {
531
			/*
532
			 * Reached end of the list without finding it.
533
			 */
534
			return (1);
535
		}
536
	} while (get_index(h) != n);
537
538
	return (edit_ifile(h));
539
}
540
541
IFILE
542
save_curr_ifile(void)
543
{
544
174
	if (curr_ifile != NULL)
545
48
		hold_ifile(curr_ifile, 1);
546
87
	return (curr_ifile);
547
}
548
549
void
550
unsave_ifile(IFILE save_ifile)
551
{
552
100
	if (save_ifile != NULL)
553
48
		hold_ifile(save_ifile, -1);
554
50
}
555
556
/*
557
 * Reedit the ifile which was previously open.
558
 */
559
void
560
reedit_ifile(IFILE save_ifile)
561
{
562
	IFILE next;
563
	IFILE prev;
564
565
	/*
566
	 * Try to reopen the ifile.
567
	 * Note that opening it may fail (maybe the file was removed),
568
	 * in which case the ifile will be deleted from the list.
569
	 * So save the next and prev ifiles first.
570
	 */
571
4
	unsave_ifile(save_ifile);
572
2
	next = next_ifile(save_ifile);
573
2
	prev = prev_ifile(save_ifile);
574
2
	if (edit_ifile(save_ifile) == 0)
575
2
		return;
576
	/*
577
	 * If can't reopen it, open the next input file in the list.
578
	 */
579
	if (next != NULL && edit_inext(next, 0) == 0)
580
		return;
581
	/*
582
	 * If can't open THAT one, open the previous input file in the list.
583
	 */
584
	if (prev != NULL && edit_iprev(prev, 0) == 0)
585
		return;
586
	/*
587
	 * If can't even open that, we're stuck.  Just quit.
588
	 */
589
	quit(QUIT_ERROR);
590
2
}
591
592
void
593
reopen_curr_ifile(void)
594
{
595
	IFILE save_ifile = save_curr_ifile();
596
	close_file();
597
	reedit_ifile(save_ifile);
598
}
599
600
/*
601
 * Edit standard input.
602
 */
603
int
604
edit_stdin(void)
605
{
606
24
	if (isatty(fd0)) {
607
		if (less_is_more) {
608
			error("Missing filename (\"more -h\" for help)",
609
			    NULL);
610
		} else {
611
			error("Missing filename (\"less --help\" for help)",
612
			    NULL);
613
		}
614
		quit(QUIT_OK);
615
	}
616
12
	return (edit("-"));
617
}
618
619
/*
620
 * Copy a file directly to standard output.
621
 * Used if standard output is not a tty.
622
 */
623
void
624
cat_file(void)
625
{
626
	int c;
627
628
	while ((c = ch_forw_get()) != EOI)
629
		putchr(c);
630
	flush(0);
631
}
632
633
/*
634
 * If the user asked for a log file and our input file
635
 * is standard input, create the log file.
636
 * We take care not to blindly overwrite an existing file.
637
 */
638
void
639
use_logfile(char *filename)
640
{
641
	int exists;
642
	int answer;
643
	PARG parg;
644
645
	if (ch_getflags() & CH_CANSEEK)
646
		/*
647
		 * Can't currently use a log file on a file that can seek.
648
		 */
649
		return;
650
651
	/*
652
	 * {{ We could use access() here. }}
653
	 */
654
	filename = shell_unquote(filename);
655
	exists = open(filename, O_RDONLY);
656
	close(exists);
657
	exists = (exists >= 0);
658
659
	/*
660
	 * Decide whether to overwrite the log file or append to it.
661
	 * If it doesn't exist we "overwrite" it.
662
	 */
663
	if (!exists || force_logfile) {
664
		/*
665
		 * Overwrite (or create) the log file.
666
		 */
667
		answer = 'O';
668
	} else {
669
		/*
670
		 * Ask user what to do.
671
		 */
672
		parg.p_string = filename;
673
		answer = query("Warning: \"%s\" exists; "
674
		    "Overwrite, Append or Don't log? ", &parg);
675
	}
676
677
loop:
678
	switch (answer) {
679
	case 'O': case 'o':
680
		/*
681
		 * Overwrite: create the file.
682
		 */
683
		logfile = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644);
684
		break;
685
	case 'A': case 'a':
686
		/*
687
		 * Append: open the file and seek to the end.
688
		 */
689
		logfile = open(filename, O_WRONLY | O_APPEND);
690
		if (lseek(logfile, (off_t)0, SEEK_END) == (off_t)-1) {
691
			close(logfile);
692
			logfile = -1;
693
		}
694
		break;
695
	case 'D': case 'd':
696
		/*
697
		 * Don't do anything.
698
		 */
699
		free(filename);
700
		return;
701
	case 'q':
702
		quit(QUIT_OK);
703
	default:
704
		/*
705
		 * Eh?
706
		 */
707
		answer = query("Overwrite, Append, or Don't log? "
708
		    "(Type \"O\", \"A\", \"D\" or \"q\") ", NULL);
709
		goto loop;
710
	}
711
712
	if (logfile < 0) {
713
		/*
714
		 * Error in opening logfile.
715
		 */
716
		parg.p_string = filename;
717
		error("Cannot write to \"%s\"", &parg);
718
		free(filename);
719
		return;
720
	}
721
	free(filename);
722
}