xref: /freebsd/contrib/less/edit.c (revision f6a3b357e9be4c6423c85eff9a847163a0d307c8)
1 /*
2  * Copyright (C) 1984-2019  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 #include "less.h"
12 #include "position.h"
13 #if HAVE_STAT
14 #include <sys/stat.h>
15 #endif
16 #if OS2
17 #include <signal.h>
18 #endif
19 
20 public int fd0 = 0;
21 
22 extern int new_file;
23 extern int errmsgs;
24 extern int cbufs;
25 extern char *every_first_cmd;
26 extern int any_display;
27 extern int force_open;
28 extern int is_tty;
29 extern int sigs;
30 extern IFILE curr_ifile;
31 extern IFILE old_ifile;
32 extern struct scrpos initial_scrpos;
33 extern void *ml_examine;
34 #if SPACES_IN_FILENAMES
35 extern char openquote;
36 extern char closequote;
37 #endif
38 
39 #if LOGFILE
40 extern int logfile;
41 extern int force_logfile;
42 extern char *namelogfile;
43 #endif
44 
45 #if HAVE_STAT_INO
46 public dev_t curr_dev;
47 public ino_t curr_ino;
48 #endif
49 
50 
51 /*
52  * Textlist functions deal with a list of words separated by spaces.
53  * init_textlist sets up a textlist structure.
54  * forw_textlist uses that structure to iterate thru the list of
55  * words, returning each one as a standard null-terminated string.
56  * back_textlist does the same, but runs thru the list backwards.
57  */
58 	public void
59 init_textlist(tlist, str)
60 	struct textlist *tlist;
61 	char *str;
62 {
63 	char *s;
64 #if SPACES_IN_FILENAMES
65 	int meta_quoted = 0;
66 	int delim_quoted = 0;
67 	char *esc = get_meta_escape();
68 	int esclen = (int) strlen(esc);
69 #endif
70 
71 	tlist->string = skipsp(str);
72 	tlist->endstring = tlist->string + strlen(tlist->string);
73 	for (s = str;  s < tlist->endstring;  s++)
74 	{
75 #if SPACES_IN_FILENAMES
76 		if (meta_quoted)
77 		{
78 			meta_quoted = 0;
79 		} else if (esclen > 0 && s + esclen < tlist->endstring &&
80 		           strncmp(s, esc, esclen) == 0)
81 		{
82 			meta_quoted = 1;
83 			s += esclen - 1;
84 		} else if (delim_quoted)
85 		{
86 			if (*s == closequote)
87 				delim_quoted = 0;
88 		} else /* (!delim_quoted) */
89 		{
90 			if (*s == openquote)
91 				delim_quoted = 1;
92 			else if (*s == ' ')
93 				*s = '\0';
94 		}
95 #else
96 		if (*s == ' ')
97 			*s = '\0';
98 #endif
99 	}
100 }
101 
102 	public char *
103 forw_textlist(tlist, prev)
104 	struct textlist *tlist;
105 	char *prev;
106 {
107 	char *s;
108 
109 	/*
110 	 * prev == NULL means return the first word in the list.
111 	 * Otherwise, return the word after "prev".
112 	 */
113 	if (prev == NULL)
114 		s = tlist->string;
115 	else
116 		s = prev + strlen(prev);
117 	if (s >= tlist->endstring)
118 		return (NULL);
119 	while (*s == '\0')
120 		s++;
121 	if (s >= tlist->endstring)
122 		return (NULL);
123 	return (s);
124 }
125 
126 	public char *
127 back_textlist(tlist, prev)
128 	struct textlist *tlist;
129 	char *prev;
130 {
131 	char *s;
132 
133 	/*
134 	 * prev == NULL means return the last word in the list.
135 	 * Otherwise, return the word before "prev".
136 	 */
137 	if (prev == NULL)
138 		s = tlist->endstring;
139 	else if (prev <= tlist->string)
140 		return (NULL);
141 	else
142 		s = prev - 1;
143 	while (*s == '\0')
144 		s--;
145 	if (s <= tlist->string)
146 		return (NULL);
147 	while (s[-1] != '\0' && s > tlist->string)
148 		s--;
149 	return (s);
150 }
151 
152 /*
153  * Close a pipe opened via popen.
154  */
155 	static void
156 close_pipe(FILE *pipefd)
157 {
158 	if (pipefd == NULL)
159 		return;
160 #if OS2
161 	/*
162 	 * The pclose function of OS/2 emx sometimes fails.
163 	 * Send SIGINT to the piped process before closing it.
164 	 */
165 	kill(pipefd->_pid, SIGINT);
166 #endif
167 	pclose(pipefd);
168 }
169 
170 /*
171  * Close the current input file.
172  */
173 	static void
174 close_file(VOID_PARAM)
175 {
176 	struct scrpos scrpos;
177 	int chflags;
178 	FILE *altpipe;
179 	char *altfilename;
180 
181 	if (curr_ifile == NULL_IFILE)
182 		return;
183 
184 	/*
185 	 * Save the current position so that we can return to
186 	 * the same position if we edit this file again.
187 	 */
188 	get_scrpos(&scrpos, TOP);
189 	if (scrpos.pos != NULL_POSITION)
190 	{
191 		store_pos(curr_ifile, &scrpos);
192 		lastmark();
193 	}
194 	/*
195 	 * Close the file descriptor, unless it is a pipe.
196 	 */
197 	chflags = ch_getflags();
198 	ch_close();
199 	/*
200 	 * If we opened a file using an alternate name,
201 	 * do special stuff to close it.
202 	 */
203 	altfilename = get_altfilename(curr_ifile);
204 	if (altfilename != NULL)
205 	{
206 		altpipe = get_altpipe(curr_ifile);
207 		if (altpipe != NULL && !(chflags & CH_KEEPOPEN))
208 		{
209 			close_pipe(altpipe);
210 			set_altpipe(curr_ifile, NULL);
211 		}
212 		close_altfile(altfilename, get_filename(curr_ifile));
213 		set_altfilename(curr_ifile, NULL);
214 	}
215 	curr_ifile = NULL_IFILE;
216 #if HAVE_STAT_INO
217 	curr_ino = curr_dev = 0;
218 #endif
219 }
220 
221 /*
222  * Edit a new file (given its name).
223  * Filename == "-" means standard input.
224  * Filename == NULL means just close the current file.
225  */
226 	public int
227 edit(filename)
228 	char *filename;
229 {
230 	if (filename == NULL)
231 		return (edit_ifile(NULL_IFILE));
232 	return (edit_ifile(get_ifile(filename, curr_ifile)));
233 }
234 
235 /*
236  * Edit a new file (given its IFILE).
237  * ifile == NULL means just close the current file.
238  */
239 	public int
240 edit_ifile(ifile)
241 	IFILE ifile;
242 {
243 	int f;
244 	int answer;
245 	int no_display;
246 	int chflags;
247 	char *filename;
248 	char *open_filename;
249 	char *alt_filename;
250 	void *altpipe;
251 	IFILE was_curr_ifile;
252 	PARG parg;
253 
254 	if (ifile == curr_ifile)
255 	{
256 		/*
257 		 * Already have the correct file open.
258 		 */
259 		return (0);
260 	}
261 
262 	/*
263 	 * We must close the currently open file now.
264 	 * This is necessary to make the open_altfile/close_altfile pairs
265 	 * nest properly (or rather to avoid nesting at all).
266 	 * {{ Some stupid implementations of popen() mess up if you do:
267 	 *    fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }}
268 	 */
269 #if LOGFILE
270 	end_logfile();
271 #endif
272 	was_curr_ifile = save_curr_ifile();
273 	if (curr_ifile != NULL_IFILE)
274 	{
275 		chflags = ch_getflags();
276 		close_file();
277 		if ((chflags & CH_HELPFILE) && held_ifile(was_curr_ifile) <= 1)
278 		{
279 			/*
280 			 * Don't keep the help file in the ifile list.
281 			 */
282 			del_ifile(was_curr_ifile);
283 			was_curr_ifile = old_ifile;
284 		}
285 	}
286 
287 	if (ifile == NULL_IFILE)
288 	{
289 		/*
290 		 * No new file to open.
291 		 * (Don't set old_ifile, because if you call edit_ifile(NULL),
292 		 *  you're supposed to have saved curr_ifile yourself,
293 		 *  and you'll restore it if necessary.)
294 		 */
295 		unsave_ifile(was_curr_ifile);
296 		return (0);
297 	}
298 
299 	filename = save(get_filename(ifile));
300 
301 	/*
302 	 * See if LESSOPEN specifies an "alternate" file to open.
303 	 */
304 	altpipe = get_altpipe(ifile);
305 	if (altpipe != NULL)
306 	{
307 		/*
308 		 * File is already open.
309 		 * chflags and f are not used by ch_init if ifile has
310 		 * filestate which should be the case if we're here.
311 		 * Set them here to avoid uninitialized variable warnings.
312 		 */
313 		chflags = 0;
314 		f = -1;
315 		alt_filename = get_altfilename(ifile);
316 		open_filename = (alt_filename != NULL) ? alt_filename : filename;
317 	} else
318 	{
319 		if (strcmp(filename, FAKE_HELPFILE) == 0 ||
320 			 strcmp(filename, FAKE_EMPTYFILE) == 0)
321 			alt_filename = NULL;
322 		else
323 			alt_filename = open_altfile(filename, &f, &altpipe);
324 
325 		open_filename = (alt_filename != NULL) ? alt_filename : filename;
326 
327 		chflags = 0;
328 		if (altpipe != NULL)
329 		{
330 			/*
331 			 * The alternate "file" is actually a pipe.
332 			 * f has already been set to the file descriptor of the pipe
333 			 * in the call to open_altfile above.
334 			 * Keep the file descriptor open because it was opened
335 			 * via popen(), and pclose() wants to close it.
336 			 */
337 			chflags |= CH_POPENED;
338 			if (strcmp(filename, "-") == 0)
339 				chflags |= CH_KEEPOPEN;
340 		} else if (strcmp(filename, "-") == 0)
341 		{
342 			/*
343 			 * Use standard input.
344 			 * Keep the file descriptor open because we can't reopen it.
345 			 */
346 			f = fd0;
347 			chflags |= CH_KEEPOPEN;
348 			/*
349 			 * Must switch stdin to BINARY mode.
350 			 */
351 			SET_BINARY(f);
352 #if MSDOS_COMPILER==DJGPPC
353 			/*
354 			 * Setting stdin to binary by default causes
355 			 * Ctrl-C to not raise SIGINT.  We must undo
356 			 * that side-effect.
357 			 */
358 			__djgpp_set_ctrl_c(1);
359 #endif
360 		} else if (strcmp(open_filename, FAKE_EMPTYFILE) == 0)
361 		{
362 			f = -1;
363 			chflags |= CH_NODATA;
364 		} else if (strcmp(open_filename, FAKE_HELPFILE) == 0)
365 		{
366 			f = -1;
367 			chflags |= CH_HELPFILE;
368 		} else if ((parg.p_string = bad_file(open_filename)) != NULL)
369 		{
370 			/*
371 			 * It looks like a bad file.  Don't try to open it.
372 			 */
373 			error("%s", &parg);
374 			free(parg.p_string);
375 			err1:
376 			if (alt_filename != NULL)
377 			{
378 				close_pipe(altpipe);
379 				close_altfile(alt_filename, filename);
380 				free(alt_filename);
381 			}
382 			del_ifile(ifile);
383 			free(filename);
384 			/*
385 			 * Re-open the current file.
386 			 */
387 			if (was_curr_ifile == ifile)
388 			{
389 				/*
390 				 * Whoops.  The "current" ifile is the one we just deleted.
391 				 * Just give up.
392 				 */
393 				quit(QUIT_ERROR);
394 			}
395 			reedit_ifile(was_curr_ifile);
396 			return (1);
397 		} else if ((f = open(open_filename, OPEN_READ)) < 0)
398 		{
399 			/*
400 			 * Got an error trying to open it.
401 			 */
402 			parg.p_string = errno_message(filename);
403 			error("%s", &parg);
404 			free(parg.p_string);
405 				goto err1;
406 		} else
407 		{
408 			chflags |= CH_CANSEEK;
409 			if (!force_open && !opened(ifile) && bin_file(f))
410 			{
411 				/*
412 				 * Looks like a binary file.
413 				 * Ask user if we should proceed.
414 				 */
415 				parg.p_string = filename;
416 				answer = query("\"%s\" may be a binary file.  See it anyway? ",
417 					&parg);
418 				if (answer != 'y' && answer != 'Y')
419 				{
420 					close(f);
421 					goto err1;
422 				}
423 			}
424 		}
425 	}
426 
427 	/*
428 	 * Get the new ifile.
429 	 * Get the saved position for the file.
430 	 */
431 	if (was_curr_ifile != NULL_IFILE)
432 	{
433 		old_ifile = was_curr_ifile;
434 		unsave_ifile(was_curr_ifile);
435 	}
436 	curr_ifile = ifile;
437 	set_altfilename(curr_ifile, alt_filename);
438 	set_altpipe(curr_ifile, altpipe);
439 	set_open(curr_ifile); /* File has been opened */
440 	get_pos(curr_ifile, &initial_scrpos);
441 	new_file = TRUE;
442 	ch_init(f, chflags);
443 
444 	if (!(chflags & CH_HELPFILE))
445 	{
446 #if LOGFILE
447 		if (namelogfile != NULL && is_tty)
448 			use_logfile(namelogfile);
449 #endif
450 #if HAVE_STAT_INO
451 		/* Remember the i-number and device of the opened file. */
452 		if (strcmp(open_filename, "-") != 0)
453 		{
454 			struct stat statbuf;
455 			int r = stat(open_filename, &statbuf);
456 			if (r == 0)
457 			{
458 				curr_ino = statbuf.st_ino;
459 				curr_dev = statbuf.st_dev;
460 			}
461 		}
462 #endif
463 		if (every_first_cmd != NULL)
464 		{
465 			ungetcc(CHAR_END_COMMAND);
466 			ungetsc(every_first_cmd);
467 		}
468 	}
469 
470 	no_display = !any_display;
471 	flush();
472 	any_display = TRUE;
473 
474 	if (is_tty)
475 	{
476 		/*
477 		 * Output is to a real tty.
478 		 */
479 
480 		/*
481 		 * Indicate there is nothing displayed yet.
482 		 */
483 		pos_clear();
484 		clr_linenum();
485 #if HILITE_SEARCH
486 		clr_hilite();
487 #endif
488 		if (strcmp(filename, FAKE_HELPFILE) && strcmp(filename, FAKE_EMPTYFILE))
489 		{
490 			char *qfilename = shell_quote(filename);
491 			cmd_addhist(ml_examine, qfilename, 1);
492 			free(qfilename);
493 		}
494 
495 		if (no_display && errmsgs > 0)
496 		{
497 			/*
498 			 * We displayed some messages on error output
499 			 * (file descriptor 2; see error() function).
500 			 * Before erasing the screen contents,
501 			 * display the file name and wait for a keystroke.
502 			 */
503 			parg.p_string = filename;
504 			error("%s", &parg);
505 		}
506 	}
507 	free(filename);
508 	return (0);
509 }
510 
511 /*
512  * Edit a space-separated list of files.
513  * For each filename in the list, enter it into the ifile list.
514  * Then edit the first one.
515  */
516 	public int
517 edit_list(filelist)
518 	char *filelist;
519 {
520 	IFILE save_ifile;
521 	char *good_filename;
522 	char *filename;
523 	char *gfilelist;
524 	char *gfilename;
525 	char *qfilename;
526 	struct textlist tl_files;
527 	struct textlist tl_gfiles;
528 
529 	save_ifile = save_curr_ifile();
530 	good_filename = NULL;
531 
532 	/*
533 	 * Run thru each filename in the list.
534 	 * Try to glob the filename.
535 	 * If it doesn't expand, just try to open the filename.
536 	 * If it does expand, try to open each name in that list.
537 	 */
538 	init_textlist(&tl_files, filelist);
539 	filename = NULL;
540 	while ((filename = forw_textlist(&tl_files, filename)) != NULL)
541 	{
542 		gfilelist = lglob(filename);
543 		init_textlist(&tl_gfiles, gfilelist);
544 		gfilename = NULL;
545 		while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
546 		{
547 			qfilename = shell_unquote(gfilename);
548 			if (edit(qfilename) == 0 && good_filename == NULL)
549 				good_filename = get_filename(curr_ifile);
550 			free(qfilename);
551 		}
552 		free(gfilelist);
553 	}
554 	/*
555 	 * Edit the first valid filename in the list.
556 	 */
557 	if (good_filename == NULL)
558 	{
559 		unsave_ifile(save_ifile);
560 		return (1);
561 	}
562 	if (get_ifile(good_filename, curr_ifile) == curr_ifile)
563 	{
564 		/*
565 		 * Trying to edit the current file; don't reopen it.
566 		 */
567 		unsave_ifile(save_ifile);
568 		return (0);
569 	}
570 	reedit_ifile(save_ifile);
571 	return (edit(good_filename));
572 }
573 
574 /*
575  * Edit the first file in the command line (ifile) list.
576  */
577 	public int
578 edit_first(VOID_PARAM)
579 {
580 	if (nifile() == 0)
581 		return (edit_stdin());
582 	curr_ifile = NULL_IFILE;
583 	return (edit_next(1));
584 }
585 
586 /*
587  * Edit the last file in the command line (ifile) list.
588  */
589 	public int
590 edit_last(VOID_PARAM)
591 {
592 	curr_ifile = NULL_IFILE;
593 	return (edit_prev(1));
594 }
595 
596 
597 /*
598  * Edit the n-th next or previous file in the command line (ifile) list.
599  */
600 	static int
601 edit_istep(h, n, dir)
602 	IFILE h;
603 	int n;
604 	int dir;
605 {
606 	IFILE next;
607 
608 	/*
609 	 * Skip n filenames, then try to edit each filename.
610 	 */
611 	for (;;)
612 	{
613 		next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
614 		if (--n < 0)
615 		{
616 			if (edit_ifile(h) == 0)
617 				break;
618 		}
619 		if (next == NULL_IFILE)
620 		{
621 			/*
622 			 * Reached end of the ifile list.
623 			 */
624 			return (1);
625 		}
626 		if (ABORT_SIGS())
627 		{
628 			/*
629 			 * Interrupt breaks out, if we're in a long
630 			 * list of files that can't be opened.
631 			 */
632 			return (1);
633 		}
634 		h = next;
635 	}
636 	/*
637 	 * Found a file that we can edit.
638 	 */
639 	return (0);
640 }
641 
642 	static int
643 edit_inext(h, n)
644 	IFILE h;
645 	int n;
646 {
647 	return (edit_istep(h, n, +1));
648 }
649 
650 	public int
651 edit_next(n)
652 	int n;
653 {
654 	return edit_istep(curr_ifile, n, +1);
655 }
656 
657 	static int
658 edit_iprev(h, n)
659 	IFILE h;
660 	int n;
661 {
662 	return (edit_istep(h, n, -1));
663 }
664 
665 	public int
666 edit_prev(n)
667 	int n;
668 {
669 	return edit_istep(curr_ifile, n, -1);
670 }
671 
672 /*
673  * Edit a specific file in the command line (ifile) list.
674  */
675 	public int
676 edit_index(n)
677 	int n;
678 {
679 	IFILE h;
680 
681 	h = NULL_IFILE;
682 	do
683 	{
684 		if ((h = next_ifile(h)) == NULL_IFILE)
685 		{
686 			/*
687 			 * Reached end of the list without finding it.
688 			 */
689 			return (1);
690 		}
691 	} while (get_index(h) != n);
692 
693 	return (edit_ifile(h));
694 }
695 
696 	public IFILE
697 save_curr_ifile(VOID_PARAM)
698 {
699 	if (curr_ifile != NULL_IFILE)
700 		hold_ifile(curr_ifile, 1);
701 	return (curr_ifile);
702 }
703 
704 	public void
705 unsave_ifile(save_ifile)
706 	IFILE save_ifile;
707 {
708 	if (save_ifile != NULL_IFILE)
709 		hold_ifile(save_ifile, -1);
710 }
711 
712 /*
713  * Reedit the ifile which was previously open.
714  */
715 	public void
716 reedit_ifile(save_ifile)
717 	IFILE save_ifile;
718 {
719 	IFILE next;
720 	IFILE prev;
721 
722 	/*
723 	 * Try to reopen the ifile.
724 	 * Note that opening it may fail (maybe the file was removed),
725 	 * in which case the ifile will be deleted from the list.
726 	 * So save the next and prev ifiles first.
727 	 */
728 	unsave_ifile(save_ifile);
729 	next = next_ifile(save_ifile);
730 	prev = prev_ifile(save_ifile);
731 	if (edit_ifile(save_ifile) == 0)
732 		return;
733 	/*
734 	 * If can't reopen it, open the next input file in the list.
735 	 */
736 	if (next != NULL_IFILE && edit_inext(next, 0) == 0)
737 		return;
738 	/*
739 	 * If can't open THAT one, open the previous input file in the list.
740 	 */
741 	if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
742 		return;
743 	/*
744 	 * If can't even open that, we're stuck.  Just quit.
745 	 */
746 	quit(QUIT_ERROR);
747 }
748 
749 	public void
750 reopen_curr_ifile(VOID_PARAM)
751 {
752 	IFILE save_ifile = save_curr_ifile();
753 	close_file();
754 	reedit_ifile(save_ifile);
755 }
756 
757 /*
758  * Edit standard input.
759  */
760 	public int
761 edit_stdin(VOID_PARAM)
762 {
763 	if (isatty(fd0))
764 	{
765 		error("Missing filename (\"less --help\" for help)", NULL_PARG);
766 		quit(QUIT_OK);
767 	}
768 	return (edit("-"));
769 }
770 
771 /*
772  * Copy a file directly to standard output.
773  * Used if standard output is not a tty.
774  */
775 	public void
776 cat_file(VOID_PARAM)
777 {
778 	int c;
779 
780 	while ((c = ch_forw_get()) != EOI)
781 		putchr(c);
782 	flush();
783 }
784 
785 #if LOGFILE
786 
787 /*
788  * If the user asked for a log file and our input file
789  * is standard input, create the log file.
790  * We take care not to blindly overwrite an existing file.
791  */
792 	public void
793 use_logfile(filename)
794 	char *filename;
795 {
796 	int exists;
797 	int answer;
798 	PARG parg;
799 
800 	if (ch_getflags() & CH_CANSEEK)
801 		/*
802 		 * Can't currently use a log file on a file that can seek.
803 		 */
804 		return;
805 
806 	/*
807 	 * {{ We could use access() here. }}
808 	 */
809 	exists = open(filename, OPEN_READ);
810 	if (exists >= 0)
811 		close(exists);
812 	exists = (exists >= 0);
813 
814 	/*
815 	 * Decide whether to overwrite the log file or append to it.
816 	 * If it doesn't exist we "overwrite" it.
817 	 */
818 	if (!exists || force_logfile)
819 	{
820 		/*
821 		 * Overwrite (or create) the log file.
822 		 */
823 		answer = 'O';
824 	} else
825 	{
826 		/*
827 		 * Ask user what to do.
828 		 */
829 		parg.p_string = filename;
830 		answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg);
831 	}
832 
833 loop:
834 	switch (answer)
835 	{
836 	case 'O': case 'o':
837 		/*
838 		 * Overwrite: create the file.
839 		 */
840 		logfile = creat(filename, 0644);
841 		break;
842 	case 'A': case 'a':
843 		/*
844 		 * Append: open the file and seek to the end.
845 		 */
846 		logfile = open(filename, OPEN_APPEND);
847 		if (lseek(logfile, (off_t)0, SEEK_END) == BAD_LSEEK)
848 		{
849 			close(logfile);
850 			logfile = -1;
851 		}
852 		break;
853 	case 'D': case 'd':
854 		/*
855 		 * Don't do anything.
856 		 */
857 		free(filename);
858 		return;
859 	case 'q':
860 		quit(QUIT_OK);
861 		/*NOTREACHED*/
862 	default:
863 		/*
864 		 * Eh?
865 		 */
866 		answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG);
867 		goto loop;
868 	}
869 
870 	if (logfile < 0)
871 	{
872 		/*
873 		 * Error in opening logfile.
874 		 */
875 		parg.p_string = filename;
876 		error("Cannot write to \"%s\"", &parg);
877 		return;
878 	}
879 	SET_BINARY(logfile);
880 }
881 
882 #endif
883