files.c 79.1 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   files.c                                                              *
 *                                                                        *
5
 *   Copyright (C) 1999-2004 Chris Allegretta                             *
Chris Allegretta's avatar
Chris Allegretta committed
6
7
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
8
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
9
10
11
12
13
14
15
16
17
18
19
20
21
 *   any later version.                                                   *
 *                                                                        *
 *   This program is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of       *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
 *   GNU General Public License for more details.                         *
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            *
 *                                                                        *
 **************************************************************************/

22
23
24
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
25

Chris Allegretta's avatar
Chris Allegretta committed
26
27
28
29
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
#include <sys/wait.h>
31
#include <utime.h>
Chris Allegretta's avatar
Chris Allegretta committed
32
33
#include <fcntl.h>
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
34
35
#include <ctype.h>
#include <dirent.h>
36
#include <pwd.h>
Chris Allegretta's avatar
Chris Allegretta committed
37
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
38
39
#include "proto.h"
#include "nano.h"
Chris Allegretta's avatar
Chris Allegretta committed
40

41
42
static file_format fmt = NIX_FILE;
	/* The format of the current file. */
43

Chris Allegretta's avatar
Chris Allegretta committed
44
45
46
/* What happens when there is no file to open? aiee! */
void new_file(void)
{
47
    fileage = make_new_node(NULL);
48
    fileage->data = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
49
    fileage->data[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
50
51
52
    filebot = fileage;
    edittop = fileage;
    current = fileage;
53
    current_x = 0;
Chris Allegretta's avatar
Chris Allegretta committed
54
    totlines = 1;
55
    totsize = 0;
56

57
58
#ifdef ENABLE_COLOR
    update_color();
59
60
    if (ISSET(COLOR_SYNTAX))
	edit_refresh();
61
#endif
Chris Allegretta's avatar
Chris Allegretta committed
62
63
}

64
65
66
67
68
69
/* We make a new line of text from buf.  buf is length len.  If
 * first_line_ins is TRUE, then we put the new line at the top of the
 * file.  Otherwise, we assume prev is the last line of the file, and
 * put our line after prev. */
filestruct *read_line(char *buf, filestruct *prev, bool *first_line_ins,
	size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
70
{
71
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
72

73
74
    /* Convert nulls to newlines.  len is the string's real length
     * here. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
75
76
    unsunder(buf, len);

77
78
79
    assert(strlen(buf) == len);

    fileptr->data = mallocstrcpy(NULL, buf);
Chris Allegretta's avatar
Chris Allegretta committed
80

81
#ifndef NANO_SMALL
82
83
    /* If it's a DOS file (CR LF), and file conversion isn't disabled,
     * strip out the CR part. */
84
85
    if (!ISSET(NO_CONVERT) && len > 0 && buf[len - 1] == '\r') {
	fileptr->data[len - 1] = '\0';
86
	totsize--;
87
88
89
    }
#endif

90
91
92
    if (*first_line_ins || fileage == NULL) {
	/* Special case: we're inserting with the cursor on the first
	 * line. */
Chris Allegretta's avatar
Chris Allegretta committed
93
94
95
	fileptr->prev = NULL;
	fileptr->next = fileage;
	fileptr->lineno = 1;
96
97
	if (*first_line_ins) {
	    *first_line_ins = FALSE;
98
	    /* If we're inserting into the first line of the file, then
99
100
	     * we want to make sure that our edit buffer stays on the
	     * first line and that fileage stays up to date. */
101
102
103
	    edittop = fileptr;
	} else
	    filebot = fileptr;
Chris Allegretta's avatar
Chris Allegretta committed
104
	fileage = fileptr;
105
106
    } else {
	assert(prev != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
107
108
109
110
111
112
113
114
115
	fileptr->prev = prev;
	fileptr->next = NULL;
	fileptr->lineno = prev->lineno + 1;
	prev->next = fileptr;
    }

    return fileptr;
}

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/* Load a file into the edit buffer.  This takes data from the file
 * struct. */
void load_file(void)
{
    current = fileage;

#ifdef ENABLE_MULTIBUFFER
    /* Add a new entry to the open_files structure. */
    add_open_file(FALSE);

    /* Reinitialize the shortcut list. */
    shortcut_init(FALSE);
#endif
}

void read_file(FILE *f, const char *filename)
Chris Allegretta's avatar
Chris Allegretta committed
132
{
133
134
135
136
137
138
139
140
141
142
    size_t num_lines = 0;
	/* The number of lines in the file. */
    size_t len = 0;
	/* The length of the current line of the file. */
    size_t i = 0;
	/* The position in the current line of the file. */
    size_t bufx = 128;
	/* The size of each chunk of the file that we read. */
    char input = '\0';
	/* The current input character. */
Chris Allegretta's avatar
Chris Allegretta committed
143
    char *buf;
144
145
146
147
148
149
150
151
	/* The buffer where we store chunks of the file. */
    filestruct *fileptr = current;
	/* The current line of the file. */
    bool first_line_ins = FALSE;
	/* Whether we're inserting with the cursor on the first line. */
    int input_int;
	/* The current value we read from the file, whether an input
	 * character or EOF. */
152
#ifndef NANO_SMALL
153
    int format = 0;
154
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
155
#endif
Chris Allegretta's avatar
Chris Allegretta committed
156

157
    buf = charalloc(bufx);
158
    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
159

160
161
    if (current != NULL) {
	if (current == fileage)
162
	    first_line_ins = TRUE;
163
164
	else
	    fileptr = current->prev;
Chris Allegretta's avatar
Chris Allegretta committed
165
    }
166

167
168
    /* For the assertion in read_line(), it must be true that if current
     * is NULL, then so is fileage. */
169
170
    assert(current != NULL || fileage == NULL);

171
#ifndef NANO_SMALL
172
173
174
    /* We don't know which file format we have yet, so assume it's a
     * *nix file for now. */
    fmt = NIX_FILE;
175
176
#endif

177
    /* Read the entire file into the file struct. */
178
    while ((input_int = getc(f)) != EOF) {
179
	input = (char)input_int;
Chris Allegretta's avatar
Chris Allegretta committed
180

181
182
	/* If it's a *nix file (LF) or a DOS file (CR LF), and file
	 * conversion isn't disabled, handle it! */
183
	if (input == '\n') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
184

185
#ifndef NANO_SMALL
186
187
	    /* If there's a CR before the LF, set format to DOS if we
	     * currently think this is a *nix file, or to both if we
188
189
	     * currently think it's a Mac file. */
	    if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r' &&
190
191
		(format == 0 || format == 2))
		format++;
192
193
194
195
#endif

	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
196

197
198
	    /* Reset the line length in preparation for the next
	     * line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
199
200
	    len = 0;

201
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
202
	    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
203
	    i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
204
#ifndef NANO_SMALL
205
206
	/* If it's a Mac file (CR without an LF), and file conversion
	 * isn't disabled, handle it! */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
207
208
	} else if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r') {

209
210
211
212
213
	    /* If we currently think the file is a *nix file, set format
	     * to Mac.  If we currently think the file is a DOS file,
	     * set format to both DOS and Mac. */
	    if (format == 0 || format == 1)
		format += 2;
214
215
216

	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
217

218
219
220
	    /* Reset the line length in preparation for the next line.
	     * Since we've already read in the next character, reset it
	     * to 1 instead of 0. */
221
	    len = 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
222

Chris Allegretta's avatar
Chris Allegretta committed
223
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
224
	    totsize++;
225
	    buf[0] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
226
	    buf[1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
227
228
	    i = 1;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
229
	} else {
230
231
	    /* Calculate the total length of the line.  It might have
	     * nulls in it, so we can't just use strlen() here. */
232
233
	    len++;

Chris Allegretta's avatar
Chris Allegretta committed
234
	    /* Now we allocate a bigger buffer 128 characters at a time.
235
236
237
	     * If we allocate a lot of space for one line, we may indeed
	     * have to use a buffer this big later on, so we don't
	     * decrease it at all.  We do free it at the end, though. */
Chris Allegretta's avatar
Chris Allegretta committed
238
239
	    if (i >= bufx - 1) {
		bufx += 128;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
240
		buf = charealloc(buf, bufx);
Chris Allegretta's avatar
Chris Allegretta committed
241
	    }
242
	    buf[i] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
243
	    buf[i + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
244
245
	    i++;
	}
246
247
248
	totsize++;
    }

249
250
    /* This conditional duplicates previous read_byte() behavior.
     * Perhaps this could use some better handling. */
251
252
253
    if (ferror(f))
	nperror(filename);
    fclose(f);
Chris Allegretta's avatar
Chris Allegretta committed
254

255
#ifndef NANO_SMALL
256
257
258
    /* If file conversion isn't disabled and the last character in this
     * file is a CR, read it in properly as a Mac format line. */
    if (len == 0 && !ISSET(NO_CONVERT) && input == '\r') {
259
260
261
	buf[0] = input;
	buf[1] = '\0';
	len = 1;
262
263
    }
#endif
264

265
266
    /* Did we not get a newline and still have stuff to do? */
    if (len > 0) {
267
#ifndef NANO_SMALL
268
	/* If file conversion isn't disabled and the last character in
269
270
	 * this file is a CR, set format to Mac if we currently think
	 * the file is a *nix file, or to both DOS and Mac if we
271
272
	 * currently think the file is a DOS file. */
	if (!ISSET(NO_CONVERT) && buf[len - 1] == '\r' &&
273
274
		(format == 0 || format == 1))
	    format += 2;
275
276
#endif

277
278
279
280
281
282
283
284
285
	/* Read in the last line properly. */
	fileptr = read_line(buf, fileptr, &first_line_ins, len);
	num_lines++;
	totsize++;
    }
    free(buf);

    /* If we didn't get a file and we don't already have one, make a new
     * file. */
286
    if (totsize == 0 || fileptr == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
287
	new_file();
Robert Siemborski's avatar
Robert Siemborski committed
288

289
    /* Did we try to insert a file of 0 bytes? */
290
291
292
293
294
295
296
297
298
299
300
301
    if (num_lines != 0) {
	if (current != NULL) {
	    fileptr->next = current;
	    current->prev = fileptr;
	    renumber(current);
	    current_x = 0;
	    placewewant = 0;
	} else if (fileptr->next == NULL) {
	    filebot = fileptr;
	    new_magicline();
	    totsize--;
	}
Chris Allegretta's avatar
Chris Allegretta committed
302
    }
303
304

#ifndef NANO_SMALL
305
    if (format == 3)
306
307
308
309
	statusbar(
		P_("Read %lu line (Converted from DOS and Mac format)",
		"Read %lu lines (Converted from DOS and Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
310
311
    else if (format == 2) {
	fmt = MAC_FILE;
312
313
314
	statusbar(P_("Read %lu line (Converted from Mac format)",
		"Read %lu lines (Converted from Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
315
316
    } else if (format == 1) {
	fmt = DOS_FILE;
317
318
319
	statusbar(P_("Read %lu line (Converted from DOS format)",
		"Read %lu lines (Converted from DOS format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
320
    } else
321
#endif
322
	statusbar(P_("Read %lu line", "Read %lu lines",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
323
		(unsigned long)num_lines),(unsigned long)num_lines);
324

325
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
326
327
}

328
329
330
331
332
333
334
335
/* Open the file (and decide if it exists).  If newfie is TRUE, display
 * "New File" if the file is missing.  Otherwise, say "[filename] not
 * found".
 *
 * Return -2 if we say "New File".  Otherwise, -1 if the file isn't
 * opened, 0 otherwise.  The file might still have an error while
 * reading with a 0 return value.  *f is set to the opened file. */
int open_file(const char *filename, bool newfie, FILE **f)
Chris Allegretta's avatar
Chris Allegretta committed
336
337
338
339
{
    int fd;
    struct stat fileinfo;

340
341
342
343
    assert(f != NULL);
    if (filename == NULL || filename[0] == '\0' ||
	    stat(filename, &fileinfo) == -1) {
	if (newfie) {
Chris Allegretta's avatar
Chris Allegretta committed
344
	    statusbar(_("New File"));
345
	    return -2;
Chris Allegretta's avatar
Chris Allegretta committed
346
	}
347
348
	statusbar(_("\"%s\" not found"), filename);
	return -1;
349
350
351
    } else if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
		S_ISBLK(fileinfo.st_mode)) {
	/* Don't open character or block files.  Sorry, /dev/sndstat! */
352
353
	statusbar(S_ISDIR(fileinfo.st_mode) ? _("\"%s\" is a directory")
		: _("File \"%s\" is a device file"), filename);
354
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
355
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
356
357
358
	statusbar(_("Error reading %s: %s"), filename, strerror(errno));
 	return -1;
     } else {
359
360
361
	/* File is A-OK.  Open it in binary mode for our own end-of-line
	 * character munging. */
	*f = fdopen(fd, "rb");
362
363

	if (*f == NULL) {
364
365
	    statusbar(_("Error reading %s: %s"), filename,
		strerror(errno));
366
	    close(fd);
367
368
	} else
	    statusbar(_("Reading File"));
Chris Allegretta's avatar
Chris Allegretta committed
369
    }
370
    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
371
372
}

373
/* This function will return the name of the first available extension
374
375
 * of a filename (starting with filename.save, then filename.save.1,
 * etc.).  Memory is allocated for the return value.  If no writable
376
 * extension exists, we return "". */
Chris Allegretta's avatar
Chris Allegretta committed
377
char *get_next_filename(const char *name)
378
379
{
    int i = 0;
380
381
    char *buf;
    size_t namelen = strlen(name);
382

383
    buf = charalloc(namelen + num_of_digits(INT_MAX) + 7);
384
    strcpy(buf, name);
385
386
    strcpy(buf + namelen, ".save");
    namelen += 5;
387

388
    while (TRUE) {
389
	struct stat fs;
390
391

	if (stat(buf, &fs) == -1)
Chris Allegretta's avatar
Chris Allegretta committed
392
	    return buf;
393
394
395
396
	if (i == INT_MAX)
	    break;

	i++;
397
	sprintf(buf + namelen, ".%d", i);
398
399
    }

Chris Allegretta's avatar
Chris Allegretta committed
400
    /* We get here only if there is no possible save file. */
401
    null_at(&buf, 0);
402
403
404
    return buf;
}

405
406
#ifndef NANO_SMALL
void execute_command(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
407
{
408
409
410
411
412
413
414
#ifdef ENABLE_MULTIBUFFER
    if (ISSET(MULTIBUFFER)) {
	/* Update the current entry in the open_files structure. */
	add_open_file(TRUE);
	new_file();
	UNSET(MODIFIED);
	UNSET(MARK_ISSET);
415
    }
416
417
418
419
420
421
422
423
424
#endif /* ENABLE_MULTIBUFFER */
    open_pipe(command);
#ifdef ENABLE_MULTIBUFFER
    /* Add this new entry to the open_files structure. */
    if (ISSET(MULTIBUFFER))
	load_file();
#endif /* ENABLE_MULTIBUFFER */
}
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
425

426
427
428
429
/* name is a file name to open.  We make a new buffer if necessary, then
 * open and read the file. */
void load_buffer(const char *name)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
430
    bool new_buffer = (fileage == NULL
431
432
#ifdef ENABLE_MULTIBUFFER
	 || ISSET(MULTIBUFFER)
433
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
434
	);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
435
436
437
438
	/* new_buffer says whether we load into this buffer or a new
	 * one.  If new_buffer is TRUE, we display "New File" if the
	 * file is not found, and if it is found we set filename and add
	 * a new open_files entry. */
439
440
441
442
    FILE *f;
    int rc;
	/* rc == -2 means that the statusbar displayed "New File".  -1
	 * means that the open failed.  0 means success. */
443

444
445
#ifndef DISABLE_OPERATINGDIR
    if (check_operating_dir(name, FALSE)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
446
447
	statusbar(_("Can't insert file from outside of %s"),
		operating_dir);
448
449
	return;
    }
450
451
#endif

452
453
454
#ifdef ENABLE_MULTIBUFFER
    /* Update the current entry in the open_files structure. */
    add_open_file(TRUE);
455
456
#endif

457
458
459
460
461
    rc = open_file(name, new_buffer, &f);

#ifdef ENABLE_MULTIBUFFER
    if (rc != -1 && ISSET(MULTIBUFFER)) {
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
462
#ifndef NANO_SMALL
463
	UNSET(MARK_ISSET);
Chris Allegretta's avatar
Chris Allegretta committed
464
#endif
465
    }
466
#endif
467
468
469
470
471
472
473

    if (rc != -1 && new_buffer) {
	filename = mallocstrcpy(filename, name);
	new_file();
    }

    if (rc == 0) {
474
475
	file_format fmt_save = fmt;

476
	read_file(f, filename);
477
478
479
480
481
482

	/* If we're not loading into a new buffer, preserve the file
	 * format. */
	if (!new_buffer)
	    fmt = fmt_save;

Chris Allegretta's avatar
Chris Allegretta committed
483
#ifndef NANO_SMALL
484
	stat(filename, &originalfilestat);
Chris Allegretta's avatar
Chris Allegretta committed
485
#endif
486
487
488
489
490
491
492
493
    }

    /* Add this new entry to the open_files structure if we have
     * multibuffer support, or to the main filestruct if we don't. */
    if (rc != -1 && new_buffer)
	load_file();
}

494
495
496
497
498
499
500
void do_insertfile(
#ifndef NANO_SMALL
	bool execute
#else
	void
#endif
	)
501
502
503
{
    int i;
    const char *msg;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
504
    char *ans = mallocstrcpy(NULL, "");
505
	/* The last answer the user typed on the statusbar. */
506
    filestruct *edittop_save = edittop;
507
    int current_y_save = current_y;
508
509
    bool at_edittop = FALSE;
	/* Whether we're at the top of the edit window. */
510

511
#ifndef DISABLE_WRAPPING
512
    wrap_reset();
513
#endif
514

515
    while (TRUE) {
516
#ifndef NANO_SMALL
517
	if (execute) {
518
#ifdef ENABLE_MULTIBUFFER
519
520
521
	    if (ISSET(MULTIBUFFER))
		msg = N_("Command to execute in new buffer [from %s] ");
	    else
522
#endif
523
524
		msg = N_("Command to execute [from %s] ");
	} else {
525
526
#endif
#ifdef ENABLE_MULTIBUFFER
527
528
529
	    if (ISSET(MULTIBUFFER)) {
		msg = N_("File to insert into new buffer [from %s] ");
	    } else
530
#endif
531
		msg = N_("File to insert [from %s] ");
532
533
#ifndef NANO_SMALL
	}
534
#endif
535

536
	i = statusq(TRUE,
537
#ifndef NANO_SMALL
538
		execute ? extcmd_list :
539
540
#endif
		insertfile_list, ans,
Chris Allegretta's avatar
Chris Allegretta committed
541
#ifndef NANO_SMALL
542
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
543
#endif
544
545
546
547
		_(msg),
#ifndef DISABLE_OPERATINGDIR
		operating_dir != NULL && strcmp(operating_dir, ".") != 0 ?
		operating_dir :
548
#endif
549
		"./");
550

551
552
	/* If we're in multibuffer mode and the filename or command is
	 * blank, open a new buffer instead of canceling. */
553
	if (i == -1 || (i == -2
554
#ifdef ENABLE_MULTIBUFFER
555
		&& !ISSET(MULTIBUFFER)
556
#endif
557
		))
558
	{
559
560
561
	    statusbar(_("Cancelled"));
	    break;
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
562
	    size_t pww_save = placewewant;
563

564
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
565

566
#if !defined(NANO_SMALL) && defined(ENABLE_MULTIBUFFER)
567
568
569
570
571
572
	    if (i == TOGGLE_MULTIBUFFER_KEY) {
		/* Don't allow toggling if we're in view mode. */
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
		continue;
	    }
573
#endif
574

575
#ifndef DISABLE_BROWSER
576
577
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
578

579
580
		if (tmp == NULL)
		    continue;
581
582
		free(answer);
		answer = tmp;
583
584
585
586

		/* We have a file now.  Get out of the statusbar prompt
		 * cleanly. */
		statusq_abort();
587
	    }
Chris Allegretta's avatar
Chris Allegretta committed
588
589
#endif

590
#ifndef NANO_SMALL
591
592
593
594
	    if (i == NANO_TOOTHERINSERT_KEY) {
		execute = !execute;
		continue;
	    }
595
#endif
596
597

#ifdef ENABLE_MULTIBUFFER
598
	    if (!ISSET(MULTIBUFFER)) {
599
#endif
600
601
		/* If we're not inserting into a new buffer, partition
		 * the filestruct so that it contains no text and hence
602
603
604
		 * looks like a new buffer, and keep track of whether
		 * the top of the partition is the top of the edit
		 * window. */
605
		filepart = partition_filestruct(current, current_x,
606
			current, current_x);
607
		at_edittop = (fileage == edittop);
608
#ifdef ENABLE_MULTIBUFFER
609
	    }
610
611
#endif

612
613
614
615
616
617
#ifndef NANO_SMALL
	    if (execute)
		execute_command(answer);
	    else {
#endif
		answer = mallocstrassn(answer, real_dir_from_tilde(answer));
618
		load_buffer(answer);
619
620
621
#ifndef NANO_SMALL
	    }
#endif
622
623

#ifdef ENABLE_MULTIBUFFER
624
	    if (!ISSET(MULTIBUFFER))
625
#endif
626
627
	    {
		filestruct *top_save = fileage;
628

629
630
631
632
633
634
635
636
637
		/* If we didn't insert into a new buffer, and we were at
		 * the top of the edit window before, set the saved
		 * value of edittop to the new top of the edit window,
		 * and update the current y-coordinate to account for
		 * the number of lines inserted. */
		if (at_edittop)
		    edittop_save = fileage;
		current_y += current_y_save;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
638
		/* If we didn't insert into a new buffer, unpartition
639
640
641
642
		 * the filestruct so that it contains all the text
		 * again.  Note that we've replaced the non-text
		 * originally in the partition with the text in the
		 * inserted file/executed command output. */
643
		unpartition_filestruct(&filepart);
644

645
646
647
		/* Renumber starting with the beginning line of the old
		 * partition. */
		renumber(top_save);
648

649
650
		/* Set edittop back to what it was before. */
		edittop = edittop_save;
651
	    }
652

653
#ifdef ENABLE_MULTIBUFFER
654
655
656
	    if (ISSET(MULTIBUFFER)) {
		/* Update the titlebar. */
		titlebar(NULL);
657

658
659
660
		/* Reinitialize the shortcut list. */
		shortcut_init(FALSE);
	    } else {
661
#endif
662
663
		/* Mark the file as modified. */
		set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
664

665
		/* Restore the old place we want. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
666
		placewewant = pww_save;
667
#ifdef ENABLE_MULTIBUFFER
668
	    }
Chris Allegretta's avatar
Chris Allegretta committed
669
670
#endif

671
672
673
674
675
676
	    /* Refresh the screen. */
	    edit_refresh();

	    break;
	}
    } /* while (TRUE) */
Chris Allegretta's avatar
Chris Allegretta committed
677

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
678
    free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
679
680
}

681
void do_insertfile_void(void)
682
{
683
#ifdef ENABLE_MULTIBUFFER
684
685
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
	statusbar(_("Key illegal in non-multibuffer mode"));
686
    else
687
#endif
688
689
690
691
692
	do_insertfile(
#ifndef NANO_SMALL
		FALSE
#endif
		);
693
694
695
696

    display_main_list();
}

697
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
698
/* Create a new openfilestruct node. */
699
openfilestruct *make_new_opennode(void)
Chris Allegretta's avatar
Chris Allegretta committed
700
{
701
702
    openfilestruct *newnode =
	(openfilestruct *)nmalloc(sizeof(openfilestruct));
Chris Allegretta's avatar
Chris Allegretta committed
703
704
705
706
707
708
    newnode->filename = NULL;
    return newnode;
}

/* Splice a node into an existing openfilestruct. */
void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
709
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
710
{
711
    assert(newnode != NULL && begin != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
712
713
714
715
716
717
718
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

719
720
/* Unlink a node from the rest of the openfilestruct, and delete it. */
void unlink_opennode(openfilestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
721
{
722
723
724
725
    assert(fileptr != NULL && fileptr->prev != NULL && fileptr->next != NULL && fileptr != fileptr->prev && fileptr != fileptr->next);
    fileptr->prev->next = fileptr->next;
    fileptr->next->prev = fileptr->prev;
    delete_opennode(fileptr);
Chris Allegretta's avatar
Chris Allegretta committed
726
727
728
729
730
}

/* Delete a node from the openfilestruct. */
void delete_opennode(openfilestruct *fileptr)
{
731
732
733
734
    assert(fileptr != NULL && fileptr->filename != NULL && fileptr->fileage != NULL);
    free(fileptr->filename);
    free_filestruct(fileptr->fileage);
    free(fileptr);
Chris Allegretta's avatar
Chris Allegretta committed
735
736
}

737
738
739
#ifdef DEBUG
/* Deallocate all memory associated with this and later files, including
 * the lines of text. */
Chris Allegretta's avatar
Chris Allegretta committed
740
741
void free_openfilestruct(openfilestruct *src)
{
742
743
744
745
    assert(src != NULL);
    while (src != src->next) {
	src = src->next;
	delete_opennode(src->prev);
Chris Allegretta's avatar
Chris Allegretta committed
746
    }
747
    delete_opennode(src);
Chris Allegretta's avatar
Chris Allegretta committed
748
}
749
#endif
Chris Allegretta's avatar
Chris Allegretta committed
750

751
/* Add/update an entry to the open_files openfilestruct.  If update is
752
 * FALSE, a new entry is created; otherwise, the current entry is
753
 * updated. */
754
void add_open_file(bool update)
755
{
756
    if (open_files == NULL && update)
757
	return;
758

759
760
761
762
763
764
765
766
767
    /* If there are no entries in open_files, make the first one. */
    if (open_files == NULL) {
	open_files = make_new_opennode();
	splice_opennode(open_files, open_files, open_files);
    /* Otherwise, if we're not updating, make a new entry for
     * open_files and splice it in after the current entry. */
    } else if (!update) {
	splice_opennode(open_files, make_new_opennode(),
		open_files->next);
768
769
770
	open_files = open_files->next;
    }

771
    /* Save the current filename. */
772
    open_files->filename = mallocstrcpy(open_files->filename, filename);
773

774
#ifndef NANO_SMALL
775
    /* Save the current file's stat. */
776
777
778
    open_files->originalfilestat = originalfilestat;
#endif

779
780
781
782
783
784
785
786
787
    /* Save the current file buffer. */
    open_files->fileage = fileage;
    open_files->filebot = filebot;

    /* Save the current top of the edit window. */
    open_files->edittop = edittop;

    /* Save the current line. */
    open_files->current = current;
788

789
790
    /* Save the current cursor position. */
    open_files->current_x = current_x;
791

792
793
    /* Save the current place we want. */
    open_files->placewewant = placewewant;
794

795
796
    /* Save the current total number of lines. */
    open_files->totlines = totlines;
797

798
799
    /* Save the current total size. */
    open_files->totsize = totsize;
800

801
802
    /* Start with no flags saved. */
    open_files->flags = 0;
803

804
805
806
    /* Save the current modification status. */
    if (ISSET(MODIFIED))
	open_files->flags |= MODIFIED;
807

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
808
#ifndef NANO_SMALL
809
810
811
812
813
    /* Save the current marking status and mark, if applicable. */
    if (ISSET(MARK_ISSET)) {
	open_files->flags |= MARK_ISSET;
	open_files->mark_beginbuf = mark_beginbuf;
	open_files->mark_beginx = mark_beginx;
Chris Allegretta's avatar
Chris Allegretta committed
814
815
    }

816
817
818
    /* Save the current file format. */
    open_files->fmt = fmt;
#endif
819
820

#ifdef DEBUG
821
    fprintf(stderr, "filename is %s\n", open_files->filename);
822
823
824
#endif
}

825
/* Read the current entry in the open_files structure and set up the
826
 * currently open file buffer using that entry's information. */
827
void load_open_file(void)
828
{
829
    assert(open_files != NULL);
830

831
    /* Restore the current filename. */
832
    filename = mallocstrcpy(filename, open_files->filename);
833

834
#ifndef NANO_SMALL
835
    /* Restore the current file's stat. */
836
837
    originalfilestat = open_files->originalfilestat;
#endif
838
839

    /* Restore the current file buffer. */
840
    fileage = open_files->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
841
    filebot = open_files->filebot;
842

843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
    /* Restore the current top of the edit window. */
    edittop = open_files->edittop;

    /* Restore the current line. */
    current = open_files->current;

    /* Restore the current cursor position. */
    current_x = open_files->current_x;

    /* Restore the current place we want. */
    placewewant = open_files->placewewant;

    /* Restore the current total number of lines. */
    totlines = open_files->totlines;

    /* Restore the current total size. */
    totsize = open_files->totsize;

    /* Restore the current modification status. */
    if (open_files->flags & MODIFIED)
Chris Allegretta's avatar
Chris Allegretta committed
863
864
865
866
867
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

#ifndef NANO_SMALL
868
869
870
871
    /* Restore the current marking status and mark, if applicable. */
    if (open_files->flags & MARK_ISSET) {
	mark_beginbuf = open_files->mark_beginbuf;
	mark_beginx = open_files->mark_beginx;
Chris Allegretta's avatar
Chris Allegretta committed
872
873
874
	SET(MARK_ISSET);
    } else
	UNSET(MARK_ISSET);
875

876
877
    /* Restore the current file format. */
    fmt = open_files->fmt;
Chris Allegretta's avatar
Chris Allegretta committed
878
#endif
879

Chris Allegretta's avatar
Chris Allegretta committed
880
881
882
#ifdef ENABLE_COLOR
    update_color();
#endif
883
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
884

885
    /* Update the titlebar. */
886
887
888
889
    clearok(topwin, FALSE);
    titlebar(NULL);
}

890
/* Open either the next or previous file buffer. */
891
void open_prevnext_file(bool next)
892
{
893
    add_open_file(TRUE);
894

895
    assert(open_files != NULL);
896

897
898
    /* If only one file buffer is open, indicate it on the statusbar and
     * get out. */
899
    if (open_files == open_files->next) {
900
	statusbar(_("No more open file buffers"));
901
	return;
902
903
    }

904
905
906
    /* Switch to the next or previous file, depending on the value of
     * next. */
    open_files = next ? open_files->next : open_files->prev;
907
908

#ifdef DEBUG
909
    fprintf(stderr, "filename is %s\n", open_files->filename);
910
911
#endif

912
    /* Load the file we switched to. */
913
914
    load_open_file();

915
    /* And indicate the switch on the statusbar. */
916
    statusbar(_("Switched to %s"),
917
      ((open_files->filename[0] == '\0') ? _("New Buffer") :
918
	open_files->filename));
919

920
921
922
923
924
#ifdef DEBUG
    dump_buffer(current);
#endif
}

925
926
/* Open the previous entry in the open_files structure.  This function
 * is used by the shortcut list. */
927
void open_prevfile_void(void)
928
{
929
    open_prevnext_file(FALSE);
930
931
}

932
933
/* Open the next entry in the open_files structure.  This function is
 * used by the shortcut list. */
934
void open_nextfile_void(void)
935
{
936
    open_prevnext_file(TRUE);
937
938
}

939
940
/* Delete an entry from the open_files filestruct.  After deletion of an
 * entry, the next entry is opened.  Return TRUE on success or FALSE if
941
 * there are no more open file buffers. */
942
bool close_open_file(void)
943
{
944
    assert(open_files != NULL);
945

946
947
    /* If only one file is open, get out. */
    if (open_files == open_files->next)
948
	return FALSE;
949

950
951
    /* Open the next file. */
    open_nextfile_void();
Chris Allegretta's avatar
Chris Allegretta committed
952

953
954
    /* Close the file we had open before. */
    unlink_opennode(open_files->prev);
955

956
    /* Reinitialize the shortcut list. */
957
    shortcut_init(FALSE);
958
    display_main_list();
959

960
    return TRUE;
961
}
962
#endif /* ENABLE_MULTIBUFFER */
963

964
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
965
/*
966
967
968
969
970
971
 * When passed "[relative path]" or "[relative path][filename]" in
 * origpath, return "[full path]" or "[full path][filename]" on success,
 * or NULL on error.  This is still done if the file doesn't exist but
 * the relative path does (since the file could exist in memory but not
 * yet on disk); it is not done if the relative path doesn't exist (since
 * the first call to chdir() will fail then).
972
 */
973
char *get_full_path(const char *origpath)
974
{
975
976
977
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
978
    char *expanded_origpath;
979

980
    /* first, get the current directory, and tack a slash onto the end of
981
       it, unless it turns out to be "/", in which case leave it alone */
982
983
984

    d_here = getcwd(NULL, PATH_MAX + 1);

985
    if (d_here != NULL) {
986
987

	align(&d_here);
988
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
989
	    d_here = charealloc(d_here, strlen(d_here) + 2);
990
991
	    strcat(d_here, "/");
	}
992
993
994
995
996

	/* stat origpath; if stat() fails, assume that origpath refers to
	   a new file that hasn't been saved to disk yet (i. e. set
	   path_only to 0); if stat() succeeds, set path_only to 0 if
	   origpath doesn't refer to a directory, or to 1 if it does */
997
	path_only = !stat(origpath, &fileinfo) && S_ISDIR(fileinfo.st_mode);
998

999
	expanded_origpath = real_dir_from_tilde(origpath);
1000
	/* save the value of origpath in both d_there and d_there_file */
1001
1002
1003
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1004

1005
1006
1007
1008
1009
1010
	/* if we have a path but no filename, tack slashes onto the ends
	   of both d_there and d_there_file, if they don't end in slashes
	   already */
	if (path_only) {
	    tmp = d_there[strlen(d_there) - 1];
	    if (tmp != '/') {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1011
		d_there = charealloc(d_there, strlen(d_there) + 2);
1012
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1013
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1014
1015
1016
1017
		strcat(d_there_file, "/");
	    }
	}

1018
1019
1020
1021
1022
	/* search for the last slash in d_there */
	last_slash = strrchr(d_there, '/');

	/* if we didn't find one, copy d_here into d_there; all data is
	   then set up */
1023
	if (last_slash == NULL)
1024
	    d_there = mallocstrcpy(d_there, d_here);
1025
	else {
1026
1027
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1028
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1029
	    null_at(&d_there, last_slash_index + 1);
1030
1031
1032

	    /* and remove all non-file elements from d_there_file (i. e.
	       everything before and including the last slash); if we
1033
	       have a path but no filename, don't do anything */
1034
1035
1036
1037
1038
1039
	    if (!path_only) {
		last_slash = strrchr(d_there_file, '/');
		last_slash++;
		strcpy(d_there_file, last_slash);
		align(&d_there_file);
	    }
1040
1041
1042

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1043
1044
1045
		/* get the full pathname, and save it back in d_there,
		   tacking a slash on the end if we have a path but no
		   filename; if the saving fails, get out */
1046
1047
1048
1049
1050
1051

		free(d_there);

		d_there = getcwd(NULL, PATH_MAX + 1);

		align(&d_there);
1052
		if (d_there != NULL) {
1053
1054
1055

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
1056
		    if (strcmp(d_there, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1057
			d_there = charealloc(d_there, strlen(d_there) + 2);
1058
1059
			strcat(d_there, "/");
		    }
1060
1061
1062
		}
		else
		    return NULL;
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
	    }

	    /* finally, go back to where we were before, d_here (no error
	       checking is done on this chdir(), because we can do
	       nothing if it fails) */
	    chdir(d_here);
	}
	
	/* all data is set up; fill in newpath */

1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
	/* if we have a path and a filename, newpath = d_there +
	   d_there_file; otherwise, newpath = d_there */
	if (!path_only) {
	    newpath = charalloc(strlen(d_there) + strlen(d_there_file) + 1);
	    strcpy(newpath, d_there);
	    strcat(newpath, d_there_file);
	}
	else {
	    newpath = charalloc(strlen(d_there) + 1);
	    strcpy(newpath, d_there);
	}
1084
1085
1086
1087
1088
1089
1090
1091
1092

	/* finally, clean up */
	free(d_there_file);
	free(d_there);
	free(d_here);
    }

    return newpath;
}
1093
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1094
1095
1096

#ifndef DISABLE_SPELLER
/*
1097
1098
1099
 * This function accepts a path and returns the full path (via
 * get_full_path()).  On error, if the path doesn't reference a
 * directory, or if the directory isn't writable, it returns NULL.
1100
 */
1101
char *check_writable_directory(const char *path)
1102
{
1103
    char *full_path = get_full_path(path);
1104
    int writable;
1105
1106
    struct stat fileinfo;

1107
    /* if get_full_path() failed, return NULL */
1108
    if (full_path == NULL)
1109
	return NULL;
1110
1111
1112

    /* otherwise, stat() the full path to see if it's writable by the
       user; set writable to 1 if it is, or 0 if it isn't */
1113
    writable = !stat(full_path, &fileinfo) && (fileinfo.st_mode & S_IWUSR);
1114
1115

    /* if the full path doesn't end in a slash (meaning get_full_path()
1116
1117
1118
1119
       found that it isn't a directory) or isn't writable, free full_path
       and return NULL */
    if (full_path[strlen(full_path) - 1] != '/' || writable == 0) {
	free(full_path);
1120
	return NULL;
1121
    }
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131

    /* otherwise, return the full path */
    return full_path;
}

/*
 * This function accepts a directory name and filename prefix the same
 * way that tempnam() does, determines the location for its temporary
 * file the same way that tempnam() does, safely creates the temporary
 * file there via mkstemp(), and returns the name of the temporary file
1132
1133
1134
1135
1136
1137
1138
 * the same way that tempnam() does.  It does not reference the value of
 * TMP_MAX because the total number of random filenames that it can
 * generate using one prefix is equal to 256**6, which is a sufficiently
 * large number to handle most cases.  Since the behavior after tempnam()
 * generates TMP_MAX random filenames is implementation-defined, my
 * implementation is to go on generating random filenames regardless of
 * it.
1139
 */
1140
1141
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1142
1143
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1144
    int filedesc;
1145

1146
1147
      /* if $TMPDIR is set and non-empty, set tempdir to it, run it through
         get_full_path(), and save the result in full_tempdir; otherwise,
1148
         leave full_tempdir set to NULL */
1149
    TMPDIR_env = getenv("TMPDIR");
1150
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1151
	full_tempdir = check_writable_directory(TMPDIR_env);
1152

1153
1154
1155
    /* if $TMPDIR is blank or isn't set, or isn't a writable
       directory, and dirname isn't NULL, try it; otherwise, leave
       full_tempdir set to NULL */
1156
    if (full_tempdir == NULL && dirname != NULL)
1157
	full_tempdir = check_writable_directory(dirname);
1158
1159
1160

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1161
    if (full_tempdir == NULL)
1162
	full_tempdir = check_writable_directory(P_tmpdir);
1163
1164

    /* if P_tmpdir didn't work, use /tmp instead */
1165
    if (full_tempdir == NULL) {
1166
1167
1168
1169
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1170
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1171
1172

    /* like tempnam(), use only the first 5 characters of the prefix */
1173
1174
1175
1176
1177
1178
1179
1180
    strncat(full_tempdir, filename_prefix, 5);
    strcat(full_tempdir, "XXXXXX");
    filedesc = mkstemp(full_tempdir);

    /* if mkstemp succeeded, close the resulting file; afterwards, it'll be
       0 bytes long, so delete it; finally, return the filename (all that's
       left of it) */
    if (filedesc != -1) {
1181
	close(filedesc);
1182
1183
	unlink(full_tempdir);
	return full_tempdir;
1184
    }
1185
1186
1187

    free(full_tempdir);
    return NULL;
1188
1189
}
#endif /* !DISABLE_SPELLER */
1190
1191

#ifndef DISABLE_OPERATINGDIR
1192
1193
1194
1195
1196
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1197
    if (operating_dir == NULL)
1198
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1199

1200
1201
1202
    full_operating_dir = get_full_path(operating_dir);

    /* If get_full_path() failed or the operating directory is
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1203
     * inaccessible, unset operating_dir. */
1204
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1205
1206
1207
1208
1209
1210
1211
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1212
/* Check to see if we're inside the operating directory.  Return 0 if we
1213
1214
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1215
 * completion will work. */
1216
int check_operating_dir(const char *currpath, int allow_tabcomp)
1217
{
1218
1219
1220
1221
    /* The char *full_operating_dir is global for mem cleanup.  It
     * should have already been initialized by init_operating_dir().
     * Also, a relative operating directory path will only be handled
     * properly if this is done. */
1222

1223
1224
1225
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1226

1227
    /* If no operating directory is set, don't bother doing anything. */
1228
    if (operating_dir == NULL)
1229
	return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1230

1231
    assert(full_operating_dir != NULL);
1232
1233

    fullpath = get_full_path(currpath);
1234
1235
1236
1237
1238
1239
1240
1241

    /* fullpath == NULL means some directory in the path doesn't exist
     * or is unreadable.  If allow_tabcomp is zero, then currpath is
     * what the user typed somewhere.  We don't want to report a
     * non-existent directory as being outside the operating directory,
     * so we return 0.  If allow_tabcomp is nonzero, then currpath
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1242
    if (fullpath == NULL)
1243
	return allow_tabcomp;
1244
1245
1246
1247
1248

    whereami1 = strstr(fullpath, full_operating_dir);
    if (allow_tabcomp)
	whereami2 = strstr(full_operating_dir, fullpath);

1249
1250
1251
1252
1253
    /* If both searches failed, we're outside the operating directory.
     * Otherwise, check the search results; if the full operating
     * directory path is not at the beginning of the full current path
     * (for normal usage) and vice versa (for tab completion, if we're
     * allowing it), we're outside the operating directory. */
1254
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1255
1256
	retval = 1;
    free(fullpath);	
1257
1258

    /* Otherwise, we're still inside it. */
1259
    return retval;
1260
}
1261
1262
#endif

1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
#ifndef NANO_SMALL
void init_backup_dir(void)
{
    char *full_backup_dir;

    if (backup_dir == NULL)
	return;

    full_backup_dir = get_full_path(backup_dir);

    /* If get_full_path() failed or the backup directory is
     * inaccessible, unset backup_dir. */
    if (full_backup_dir == NULL ||
	full_backup_dir[strlen(full_backup_dir) - 1] != '/') {
	free(full_backup_dir);
	free(backup_dir);
	backup_dir = NULL;
    } else {
	free(backup_dir);
	backup_dir = full_backup_dir;
    }
}
#endif

1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
/* Read from inn, write to out.  We assume inn is opened for reading,
 * and out for writing.  We return 0 on success, -1 on read error, -2 on
 * write error. */
int copy_file(FILE *inn, FILE *out)
{
    char buf[BUFSIZ];
    size_t charsread;
    int retval = 0;

    assert(inn != NULL && out != NULL);
    do {
	charsread = fread(buf, sizeof(char), BUFSIZ, inn);
	if (charsread == 0 && ferror(inn)) {
	    retval = -1;
	    break;
	}
	if (fwrite(buf, sizeof(char), charsread, out) < charsread) {
	    retval = -2;
	    break;
	}
    } while (charsread > 0);
    if (fclose(inn) == EOF)
	retval = -1;
    if (fclose(out) == EOF)
	retval = -2;
    return retval;
}

1315
/* Write a file out.  If tmp is FALSE, we set the umask to disallow
1316
1317
1318
 * anyone else from accessing the file, we don't set the global variable
 * filename to its name, and we don't print out how many lines we wrote
 * on the statusbar.
Chris Allegretta's avatar
Chris Allegretta committed
1319
 *
1320
1321
 * tmp means we are writing a temporary file in a secure fashion.  We
 * use it when spell checking or dumping the file on an error.
1322
 *
1323
1324
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1325
 *
1326
1327
 * nonamechange means don't change the current filename.  It is ignored
 * if tmp is FALSE or if we're appending/prepending.
1328
1329
 *
 * Return -1 on error, 1 on success. */
1330
1331
int write_file(const char *name, bool tmp, int append, bool
	nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1332
{
1333
1334
    int retval = -1;
	/* Instead of returning in this function, you should always
1335
1336
1337
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
    const filestruct *fileptr = fileage;
1338
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1339
	/* The file descriptor we use. */
1340
    mode_t original_umask = 0;
1341
	/* Our umask, from when nano started. */
1342
    bool realexists;
1343
	/* The result of stat().  TRUE if the file exists, FALSE
1344
	 * otherwise.  If name is a link that points nowhere, realexists
1345
	 * is FALSE. */
1346
1347
    struct stat st;
	/* The status fields filled in by stat(). */
1348
    bool anyexists;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1349
	/* The result of lstat().  Same as realexists unless name is a
1350
1351
1352
1353
	 * link. */
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1354
	/* name after tilde expansion. */
1355
1356
1357
1358
    FILE *f;
	/* The actual file, realname, we are writing to. */
    char *tempname = NULL;
	/* The temp file name we write to on prepend. */
Chris Allegretta's avatar
Chris Allegretta committed
1359

1360
    assert(name != NULL);
1361
    if (name[0] == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1362
	return -1;
1363
1364
    if (!tmp)
	titlebar(NULL);
1365

1366
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1367

1368
#ifndef DISABLE_OPERATINGDIR
1369
    /* If we're writing a temporary file, we're probably going outside
1370
     * the operating directory, so skip the operating directory test. */
1371
    if (!tmp && check_operating_dir(realname, FALSE) != 0) {
1372
1373
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1374
1375
1376
    }
#endif

1377
    anyexists = (lstat(realname, &lst) != -1);
1378

1379
1380
1381
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
1382

1383
1384
1385
    /* If NOFOLLOW_SYMLINKS is set, it doesn't make sense to prepend or
     * append to a symlink.  Here we warn about the contradiction. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode)) {
1386
1387
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1388
1389
1390
	goto cleanup_and_exit;
    }

1391
    /* Save the state of file at the end of the symlink (if there is
1392
     * one). */
1393
    realexists = (stat(realname, &st) != -1);
1394

1395
1396
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1397
1398
1399
1400
     * temporary, and the file already exists.  Furthermore, if we
     * aren't appending, prepending, or writing a selection, we backup
     * only if the file has not been modified by someone else since nano
     * opened it. */
1401
    if (ISSET(BACKUP_FILE) && !tmp && realexists &&
1402
1403
1404
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1405
	FILE *backup_file;
1406
	char *backupname;
1407
	struct utimbuf filetime;
1408
	int copy_status;
1409

1410
	/* Save the original file's access and modification times. */
1411
1412
1413
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1414
	/* Open the original file to copy to the backup. */
1415
	f = fopen(realname, "rb");
1416
	if (f == NULL) {
1417
	    statusbar(_("Error reading %s: %s"), realname,
1418
		strerror(errno));
1419
	    goto cleanup_and_exit;
1420
1421
	}

1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
	/* If backup_dir is set, we set backupname to
	 * backup_dir/backupname~, where backupnae is the canonicalized
	 * absolute pathname of realname with every '/' replaced with a
	 * '!'.  This means that /home/foo/file is backed up in
	 * backup_dir/!home!foo!file~. */
	if (backup_dir != NULL) {
	    char *canon_realname = get_full_path(realname);
	    size_t i;

	    if (canon_realname == NULL)
		/* If get_full_path() failed, we don't have a
		 * canonicalized absolute pathname, so just use the
		 * filename portion of the pathname.  We use tail() so
		 * that e.g. ../backupname will be backed up in
		 * backupdir/backupname~ instead of
		 * backupdir/../backupname~. */
		canon_realname = mallocstrcpy(NULL, tail(realname));
	    else {
		for (i = 0; canon_realname[i] != '\0'; i++) {
		    if (canon_realname[i] == '/')
			canon_realname[i] = '!';
		}
	    }

	    backupname = charalloc(strlen(backup_dir) +
		strlen(canon_realname) + 2);
	    sprintf(backupname, "%s%s~", backup_dir, canon_realname);
	    free(canon_realname);
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1454

1455
1456
1457
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1458
	backup_file = fopen(backupname, "wb");
1459
1460
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1461
1462
	    statusbar(_("Error writing %s: %s"), backupname,
		strerror(errno));
1463
	    free(backupname);
1464
1465
1466
1467
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1468
1469
1470
	}

#ifdef DEBUG
1471
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1472
1473
#endif

1474
1475
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1476

1477
	/* And set metadata. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1478
1479
1480
	if (copy_status != 0 || chown(backupname,
		originalfilestat.st_uid, originalfilestat.st_gid) == -1
		|| utime(backupname, &filetime) == -1) {
1481
1482
	    free(backupname);
	    if (copy_status == -1)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1483
1484
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1485
1486
1487
1488
1489
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1490
1491
	free(backupname);
    }
1492
#endif /* !NANO_SMALL */
1493

1494
1495
1496
1497
1498
1499
    /* If NOFOLLOW_SYMLINKS and the file is a link, we aren't doing
     * prepend or append.  So we delete the link first, and just
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1500
	goto cleanup_and_exit;
1501
    }
1502

1503
1504
    original_umask = umask(0);
    umask(original_umask);
1505

1506
    /* If we create a temp file, we don't let anyone else access it.  We
1507
     * create a temp file if tmp is TRUE or if we're prepending. */
1508
1509
1510
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

1511
    /* If we're prepending, copy the file to a temp file. */
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
    if (append == 2) {
	int fd_source;
	FILE *f_source = NULL;

	tempname = charalloc(strlen(realname) + 8);
	strcpy(tempname, realname);
	strcat(tempname, ".XXXXXX");
	fd = mkstemp(tempname);
	f = NULL;
	if (fd != -1) {
	    f = fdopen(fd, "wb");
	    if (f == NULL)
		close(fd);
	}
	if (f == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1527
1528
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1529
	    unlink(tempname);
1530
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1531
	}
1532

1533
1534
1535
1536
1537
1538
1539
	fd_source = open(realname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
	}
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1540
1541
	    statusbar(_("Error reading %s: %s"), realname,
		strerror(errno));
1542
1543
1544
1545
1546
1547
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

	if (copy_file(f_source, f) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1548
1549
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1550
	    unlink(tempname);
1551
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1552
1553
1554
	}
    }

1555
1556
    /* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This is
     * now copied from joe, because wiggy says so *shrug*. */
1557
1558
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
1559
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1560

1561
    /* Set the umask back to the user's original value. */
1562
1563
1564
1565
1566
    umask(original_umask);

    /* First, just give up if we couldn't even open the file. */
    if (fd == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1567
1568
1569
	/* tempname has been set only if we're prepending. */
	if (tempname != NULL)
	    unlink(tempname);
1570
1571
	goto cleanup_and_exit;
    }
1572

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1573
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1574
    if (f == NULL) {
1575
1576
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1577
	goto cleanup_and_exit;
1578
1579
    }

1580
    /* There might not be a magicline.  There won't be when writing out
1581
1582
1583
1584
1585
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1586

1587
	/* Newlines to nulls, just before we write to disk. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1588
1589
	sunder(fileptr->data);

1590
	size = fwrite(fileptr->data, sizeof(char), data_len, f);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1591

1592
	/* Nulls to newlines; data_len is the string's real length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1593
1594
	unsunder(fileptr->data, data_len);

1595
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1596
1597
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1598
	    fclose(f);
1599
	    goto cleanup_and_exit;
1600
	}
1601
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1602
	if (fmt == DOS_FILE || fmt == MAC_FILE) {
1603
	    if (putc('\r', f) == EOF) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1604
1605
		statusbar(_("Error writing %s: %s"), realname,
			strerror(errno));
1606
1607
1608
		fclose(f);
		goto cleanup_and_exit;
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1609
	}
Chris Allegretta's avatar
Chris Allegretta committed
1610

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1611
	if (fmt != MAC_FILE) {
1612
#endif
1613
	    if (putc('\n', f) == EOF) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1614
1615
		statusbar(_("Error writing %s: %s"), realname,
			strerror(errno));
1616
1617
1618
		fclose(f);
		goto cleanup_and_exit;
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1619
1620
1621
#ifndef NANO_SMALL
	}
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1622
1623
1624
1625
1626

	fileptr = fileptr->next;
	lineswritten++;
    }

1627
    /* If we're prepending, open the temp file, and append it to f. */
1628
    if (append == 2) {
1629
1630
1631
1632
1633
1634
1635
1636
	int fd_source;
	FILE *f_source = NULL;

	fd_source = open(tempname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1637
	}
1638
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1639
1640
	    statusbar(_("Error reading %s: %s"), tempname,
		strerror(errno));
1641
	    fclose(f);
1642
	    goto cleanup_and_exit;
1643
1644
	}

1645
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1646
1647
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1648
	    goto cleanup_and_exit;
1649
	}
1650
1651
1652
1653
    } else if (fclose(f) == EOF) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	unlink(tempname);
	goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1654
    }
1655

1656
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1657
	if (!nonamechange) {
1658
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1659
1660
#ifdef ENABLE_COLOR
	    update_color();
1661
1662
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1663
1664
#endif
	}
1665

1666
1667
1668
1669
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1670
1671
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1672
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1673
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1674
    }
1675
1676
1677
1678
1679

    retval = 1;

  cleanup_and_exit:
    free(realname);
1680
    free(tempname);
1681
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1682
1683
}

1684
1685
1686
#ifndef NANO_SMALL
/* Write a marked selection from a file out.  First, set fileage and
 * filebot as the top and bottom of the mark, respectively.  Then call
1687
1688
1689
1690
 * write_file() with the values of name, temp, and append, and with
 * nonamechange set to TRUE so that we don't change the current
 * filename.  Finally, set fileage and filebot back to their old values
 * and return. */
1691
int write_marked(const char *name, bool tmp, int append)
1692
1693
{
    int retval = -1;
1694
    bool old_modified = ISSET(MODIFIED);
1695
	/* write_file() unsets the MODIFIED flag. */
1696
1697
1698
1699
1700
1701
1702
1703
    bool added_magicline;
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

    /* Partition the filestruct so that it contains only the marked
     * text. */
    mark_order((const filestruct **)&top, &top_x,
1704
	(const filestruct **)&bot, &bot_x, NULL);
1705
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1706
1707

    /* If the line at filebot is blank, treat it as the magicline and
1708
1709
1710
1711
1712
     * hence the end of the file.  Otherwise, add a magicline and treat
     * it as the end of the file. */
    added_magicline = (filebot->data[0] != '\0');
    if (added_magicline)
	new_magicline();
1713

1714
    retval = write_file(name, tmp, append, TRUE);
1715

1716
1717
1718
1719
1720
1721
    /* If we added a magicline, remove it now. */
    if (added_magicline)
	remove_magicline();

    /* Unpartition the filestruct so that it contains all the text
     * again. */
1722
    unpartition_filestruct(&filepart);
1723

1724
    if (old_modified)
1725
1726
1727
1728
1729
1730
	set_modified();

    return retval;
}
#endif /* !NANO_SMALL */

1731
int do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1732
{
1733
    int i;
1734
1735
1736
    int retval = 0, append = 0;
    char *ans;
	/* The last answer the user typed on the statusbar. */
1737
#ifdef NANO_EXTRA
1738
    static bool did_cred = FALSE;
1739
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1740

1741
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1742
    currshortcut = writefile_list;
1743
1744
#endif

1745
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1746
1747
1748
1749
1750
	retval = write_file(filename, FALSE, 0, FALSE);

	/* Write succeeded. */
	if (retval == 1)
	    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1751
1752
    }

1753
#ifndef NANO_SMALL
1754
    if (ISSET(MARK_ISSET) && !exiting)
1755
	ans = mallocstrcpy(NULL, "");
1756
1757
    else
#endif
1758
	ans = mallocstrcpy(NULL, filename);
1759
1760
1761
1762

    while (TRUE) {
	const char *msg;
#ifndef NANO_SMALL
1763
1764
	const char *formatstr, *backupstr;

1765
	if (fmt == DOS_FILE)
1766
	   formatstr = N_(" [DOS Format]");
1767
	else if (fmt == MAC_FILE)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1768
	   formatstr = N_(" [Mac Format]");
1769
1770
1771
	else
	   formatstr = "";

1772
	if (ISSET(BACKUP_FILE))
1773
	   backupstr = N_(" [Backup]");
1774
1775
1776
	else
	   backupstr = "";

1777
	/* Be nice to the translation folks. */
1778
	if (ISSET(MARK_ISSET) && !exiting) {
1779
	    if (append == 2)
1780
		msg = N_("Prepend Selection to File");
Chris Allegretta's avatar
Chris Allegretta committed
1781
	    else if (append == 1)
1782
		msg = N_("Append Selection to File");
1783
	    else
1784
		msg = N_("Write Selection to File");
1785
1786
	} else
#endif /* !NANO_SMALL */
1787
	if (append == 2)
1788
	    msg = N_("File Name to Prepend to");
Chris Allegretta's avatar
Chris Allegretta committed
1789
	else if (append == 1)
1790
	    msg = N_("File Name to Append to");
1791
	else
1792
	    msg = N_("File Name to Write");
1793

1794
1795
1796
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
1797
	i = statusq(!ISSET(RESTRICTED) || filename[0] == '\0',
1798
		writefile_list, ans,
1799
#ifndef NANO_SMALL
1800
		NULL, "%s%s%s", _(msg), formatstr, backupstr
1801
#else
1802
		"%s", _(msg)
1803
1804
1805
#endif
		);

1806
	if (i < 0) {
1807
	    statusbar(_("Cancelled"));
1808
1809
1810
1811
	    retval = -1;
	    break;
	} else {
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
1812

1813
#ifndef DISABLE_BROWSER
1814
1815
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
1816

1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
		currshortcut = writefile_list;

		if (tmp == NULL)
		    continue;
		free(answer);
		answer = tmp;

		/* We have a file now.  Get out of the statusbar prompt
		 * cleanly. */
		statusq_abort();
	    } else
1828
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1829
#ifndef NANO_SMALL
1830
	    if (i == TOGGLE_DOS_KEY) {
1831
		fmt = (fmt == DOS_FILE) ? NIX_FILE : DOS_FILE;
1832
1833
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1834
		fmt = (fmt == MAC_FILE) ? NIX_FILE : MAC_FILE;
1835
1836
1837
1838
1839
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1840
#endif /* !NANO_SMALL */
1841
1842
1843
1844
1845
1846
1847
	    if (i == NANO_PREPEND_KEY) {
		append = (append == 2) ? 0 : 2;
		continue;
	    } else if (i == NANO_APPEND_KEY) {
		append = (append == 1) ? 0 : 1;
		continue;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1848

Chris Allegretta's avatar
Chris Allegretta committed
1849
#ifdef DEBUG
1850
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1851
#endif
1852
1853

#ifdef NANO_EXTRA
1854
1855
1856
1857
1858
1859
1860
	    if (exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0 && !did_cred) {
		do_credits();
		did_cred = TRUE;
		retval = -1;
		break;
	    }
1861
#endif
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
	    if (append == 0 && strcmp(answer, filename) != 0) {
		struct stat st;

		if (!stat(answer, &st)) {
		    i = do_yesno(FALSE, _("File exists, OVERWRITE ? "));
		    if (i == 0 || i == -1)
			continue;
		/* If we're using restricted mode, we aren't allowed to
		 * change the name of a file once it has one because
		 * that would allow reading from or writing to files not
		 * specified on the command line.  In this case, don't
		 * bother showing the "Different Name" prompt. */
		} else if (!ISSET(RESTRICTED) && filename[0] != '\0'
1875
#ifndef NANO_SMALL
1876
			&& (exiting || !ISSET(MARK_ISSET))
1877
#endif
1878
			) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1879
1880
		    i = do_yesno(FALSE,
			_("Save file under DIFFERENT NAME ? "));
1881
1882
1883
		    if (i == 0 || i == -1)
			continue;
		}
1884
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1885

1886
#ifndef NANO_SMALL
1887
1888
1889
1890
1891
1892
1893
	    /* Here's where we allow the selected text to be written to
	     * a separate file.  If we're using restricted mode, this is
	     * disabled since it allows reading from or writing to files
	     * not specified on the command line. */
	    if (!ISSET(RESTRICTED) && !exiting && ISSET(MARK_ISSET))
		retval = write_marked(answer, FALSE, append);
	    else
1894
#endif /* !NANO_SMALL */
1895
		retval = write_file(answer, FALSE, append, FALSE);
1896

1897
#ifdef ENABLE_MULTIBUFFER
1898
1899
1900
1901
	    /* If we're not about to exit, update the current entry in
	     * the open_files structure. */
	    if (!exiting)
		add_open_file(TRUE);
1902
#endif
1903
1904
1905

	    break;
	}
1906
    } /* while (TRUE) */
1907
1908
1909

    free(ans);
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1910
1911
}

1912
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1913
{
1914
    do_writeout(FALSE);
1915
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1916
}
Chris Allegretta's avatar
Chris Allegretta committed
1917

1918
/* Return a malloc()ed string containing the actual directory, used
1919
1920
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1921
{
Chris Allegretta's avatar
Chris Allegretta committed
1922
    char *dirtmp = NULL;
1923

1924
    if (buf[0] == '~') {
1925
1926
1927
1928
1929
1930
1931
	size_t i;
	const struct passwd *userdata;

	/* Figure how how much of the str we need to compare */
	for (i = 1; buf[i] != '/' && buf[i] != '\0'; i++)
	    ;

1932
1933
	/* Determine home directory using getpwuid() or getpwent(),
	   don't rely on $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
1934
1935
1936
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
1937
1938
1939
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1940
		strncmp(userdata->pw_name, buf + 1, i - 1) != 0);
Chris Allegretta's avatar
Chris Allegretta committed
1941
1942
	}
	endpwent();
1943

1944
	if (userdata != NULL) {	/* User found */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1945
1946
	    dirtmp = charalloc(strlen(userdata->pw_dir) +
		strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1947
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1948
	}
1949
    }
1950

1951
1952
1953
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1954
    return dirtmp;
1955
1956
}

Chris Allegretta's avatar
Chris Allegretta committed
1957
#ifndef DISABLE_TABCOMP
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1958
1959
1960
/* Tack a slash onto the string we're completing if it's a directory.
 * We assume there is room for one more character on the end of buf.
 * The return value says whether buf is a directory. */
1961
int append_slash_if_dir(char *buf, bool *lastwastab, int *place)
1962
{
1963
    char *dirptr = real_dir_from_tilde(buf);
1964
    struct stat fileinfo;
1965
    int ret = 0;
1966

1967
    assert(dirptr != buf);
1968

1969
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
1970
	strncat(buf, "/", 1);
1971
	(*place)++;
1972
	/* now we start over again with # of tabs so far */
1973
	*lastwastab = FALSE;
1974
	ret = 1;
1975
1976
    }

1977
    free(dirptr);
1978
    return ret;
1979
}
Chris Allegretta's avatar
Chris Allegretta committed
1980
1981

/*
Chris Allegretta's avatar
Chris Allegretta committed
1982
1983
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1984
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
 *
 * Termios command line History and Editting, originally
 * intended for NetBSD sh (ash)
 * Copyright (c) 1999
 *      Main code:            Adam Rogoyski <rogoyski@cs.utexas.edu>
 *      Etc:                  Dave Cinege <dcinege@psychosis.com>
 *  Majorly adjusted/re-written for busybox:
 *                            Erik Andersen <andersee@debian.org>
 *
 * You may use this code as you wish, so long as the original author(s)
 * are attributed in any redistributions of the source code.
 * This code is 'as is' with no warranty.
 * This code may safely be consumed by a BSD or GPL license.
 */

char **username_tab_completion(char *buf, int *num_matches)
{
2002
    char **matches = (char **)NULL;
2003
    char *matchline = NULL;
2004
    struct passwd *userdata;
2005

2006
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2007
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2008

2009
    strcat(buf, "*");
2010

2011
    while ((userdata = getpwent()) != NULL) {
2012

2013
	if (check_wildcard_match(userdata->pw_name, &buf[1])) {
2014
2015
2016
2017

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2018

2019
2020
2021
2022
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2023
	    if (operating_dir != NULL) {
2024
		if (check_operating_dir(userdata->pw_dir, TRUE) != 0)
2025
2026
2027
2028
		    continue;
	    }
#endif

2029
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2030
2031
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
2032
	    ++(*num_matches);
2033

2034
2035
2036
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2037
	}
2038
2039
    }
    endpwent();
2040

2041
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2042
2043
2044
2045
2046
2047
2048
}

/* This was originally called exe_n_cwd_tab_completion, but we're not
   worried about executables, only filenames :> */

char **cwd_tab_completion(char *buf, int *num_matches)
{
Chris Allegretta's avatar
Chris Allegretta committed
2049
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2050
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2051
2052
2053
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2054
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2055
2056
2057
2058

    /* Stick a wildcard onto the buf, for later use */
    strcat(buf, "*");

2059
    /* Okie, if there's a / in the buffer, strip out the directory part */
2060
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2061
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2062
2063
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2064
	    tmp--;
2065

Chris Allegretta's avatar
Chris Allegretta committed
2066
2067
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2068
2069
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2070

Chris Allegretta's avatar
Chris Allegretta committed
2071
    } else {
2072

Chris Allegretta's avatar
Chris Allegretta committed
2073
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2074
2075
2076
2077
2078
2079
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2080
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2081
2082
2083
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2084

Chris Allegretta's avatar
Chris Allegretta committed
2085
2086
2087
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2088
2089

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2090
    fprintf(stderr, "\nDir = %s\n", dirname);
2091
2092
2093
2094
2095
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2096
    dir = opendir(dirname);
2097
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2098
2099
2100
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2101
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2102
2103
2104
2105
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2106
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2107
2108
#endif
	/* See if this matches */
2109
	if (check_wildcard_match(next->d_name, tmp)) {
2110
2111
2112
2113

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2114
2115
2116
2117
2118
2119
2120
2121

#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match; to
	       properly do operating directory checking, we have to add the
	       directory name to the beginning of the proposed match
	       before we check it */

2122
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2123
2124
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2125
2126
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2127
		if (check_operating_dir(tmp2, TRUE) != 0) {
2128
2129
2130
2131
2132
2133
2134
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2135
	    tmp2 = NULL;
2136
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2137
2138
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2139
	    ++*num_matches;
2140
2141
2142
2143

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2144
2145
	}
    }
2146
2147
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2148

2149
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2150
2151
}

Chris Allegretta's avatar
Chris Allegretta committed
2152
2153
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
2154
2155
char *input_tab(char *buf, int place, bool *lastwastab, int *newplace,
	bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2156
2157
{
    /* Do TAB completion */
2158
    static int num_matches = 0, match_matches = 0;
2159
    static char **matches = (char **)NULL;
2160
    int pos = place, i = 0, col = 0, editline = 0;
2161
    int longestname = 0, is_dir = 0;
2162
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2163

2164
    *list = FALSE;
2165

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2166
    if (*lastwastab == FALSE) {
2167
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2168

2169
	*lastwastab = TRUE;
2170

Chris Allegretta's avatar
Chris Allegretta committed
2171
2172
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2173
2174
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2175

2176
2177
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2178
2179

	/* skip any leading white space */
2180
	while (*tmp && isblank(*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2181
2182
2183
2184
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2185
2186
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2187
	    free(matches);
2188
	    matches = (char **)NULL;
2189
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2190
2191
2192
2193
2194
	}

	/* If the word starts with `~' and there is no slash in the word, 
	 * then try completing this word as a username. */

Chris Allegretta's avatar
Chris Allegretta committed
2195
2196
2197
2198
2199
	/* If the original string begins with a tilde, and the part
	   we're trying to tab-complete doesn't contain a slash, copy
	   the part we're tab-completing into buf, so tab completion
	   will result in buf's containing only the tab-completed
	   username. */
2200
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2201
	    buf = mallocstrcpy(buf, tmp);
2202
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2203
	}
2204
2205
2206
2207
2208
2209
	/* If we're in the middle of the original line, copy the string
	   only up to the cursor position into buf, so tab completion
	   will result in buf's containing only the tab-completed
	   path/filename. */
	else if (strlen(buf) > strlen(tmp))
	    buf = mallocstrcpy(buf, tmp);
Chris Allegretta's avatar
Chris Allegretta committed
2210
2211
2212

	/* Try to match everything in the current working directory that
	 * matches.  */
2213
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2214
2215
2216
	    matches = cwd_tab_completion(tmp, &num_matches);

	/* Don't leak memory */
2217
	free(matchbuf);
Chris Allegretta's avatar
Chris Allegretta committed
2218

2219
2220
2221
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2222
	/* Did we find exactly one match? */
2223
	switch (num_matches) {
2224
	case 0:
2225
	    blank_edit();
2226
	    wrefresh(edit);
2227
2228
	    break;
	case 1:
2229

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2230
	    buf = charealloc(buf, strlen(buf) + strlen(matches[0]) + 1);
2231

2232
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2233
2234
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2235
		tmp++;
2236
	    } else
2237
2238
		tmp = buf;

2239
	    if (strcmp(tmp, matches[0]) == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2240
		is_dir = append_slash_if_dir(buf, lastwastab, newplace);
2241

2242
	    if (is_dir != 0)
2243
		break;
2244
2245

	    copyto = tmp;
2246
2247
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2248
2249
		tmp++;

2250
	    /* write out the matched name */
2251
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2252
2253
	    *newplace += strlen(matches[0]) - pos;

2254
2255
2256
2257
2258
2259
2260
	    /* if an exact match is typed in and Tab is pressed,
	       *newplace will now be negative; in that case, make it
	       zero, so that the cursor will stay where it is instead of
	       moving backward */
	    if (*newplace < 0)
		*newplace = 0;

2261
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2262
	    append_slash_if_dir(buf, lastwastab, newplace);
2263

2264
2265
	    break;
	default:
2266
	    /* Check to see if all matches share a beginning, and, if so,
2267
	       tack it onto buf and then beep */
2268

2269
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2270
2271
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2272
		tmp++;
2273
	    } else
2274
2275
		tmp = buf;

2276
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2277
		 pos <= strlen(matches[0]); pos++)
2278
2279
		tmp++;

2280
	    while (TRUE) {
2281
2282
2283
2284
2285
2286
2287
2288
		match_matches = 0;

		for (i = 0; i < num_matches; i++) {
		    if (matches[i][pos] == 0)
			break;
		    else if (matches[i][pos] == matches[0][pos])
			match_matches++;
		}
2289
2290
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2291
		    /* All the matches have the same character at pos+1,
2292
		       so paste it into buf... */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2293
		    buf = charealloc(buf, strlen(buf) + 2);
2294
		    strncat(buf, matches[0] + pos, 1);
2295
		    *newplace += 1;
2296
		    pos++;
2297
		} else {
2298
2299
2300
2301
2302
		    beep();
		    break;
		}
	    }
	}
Chris Allegretta's avatar
Chris Allegretta committed
2303
2304
2305
2306
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2307
	if (matches != NULL && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2308
2309
2310
2311
2312

	    /* Blank the edit window, and print the matches out there */
	    blank_edit();
	    wmove(edit, 0, 0);

2313
	    editline = 0;
2314

2315
2316
2317
2318
2319
2320
2321
2322
	    /* Figure out the length of the longest filename */
	    for (i = 0; i < num_matches; i++)
		if (strlen(matches[i]) > longestname)
		    longestname = strlen(matches[i]);

	    if (longestname > COLS - 1)
		longestname = COLS - 1;

2323
	    foo = charalloc(longestname + 5);
2324

Chris Allegretta's avatar
Chris Allegretta committed
2325
2326
	    /* Print the list of matches */
	    for (i = 0, col = 0; i < num_matches; i++) {
2327

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2328
2329
		/* make each filename shown be the same length as the
		   longest filename, with two spaces at the end */
2330
		snprintf(foo, longestname + 1, "%s", matches[i]);
2331
2332
2333
2334
2335
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2336
2337
		/* Disable el cursor */
		curs_set(0);
2338
2339
2340
2341
2342
2343
		/* now, put the match on the screen */
		waddnstr(edit, foo, strlen(foo));
		col += strlen(foo);

		/* And if the next match isn't going to fit on the
		   line, move to the next one */
2344
		if (col > COLS - longestname && i + 1 < num_matches) {
2345
2346
		    editline++;
		    wmove(edit, editline, 0);
2347
2348
2349
2350
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2351
2352
2353
		    col = 0;
		}
	    }
2354
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2355
	    wrefresh(edit);
2356
	    *list = TRUE;
2357
2358
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2359
2360
    }

2361
2362
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
2363
    if (*list == FALSE)
2364
	edit_refresh();
2365
2366
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2367
}
2368
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2369

2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
/* Only print the last part of a path; isn't there a shell
 * command for this? */
const char *tail(const char *foo)
{
    const char *tmp = foo + strlen(foo);

    while (*tmp != '/' && tmp != foo)
	tmp--;

    if (*tmp == '/')
	tmp++;

    return tmp;
}

2385
#ifndef DISABLE_BROWSER
2386
/* Our sort routine for file listings -- sort directories before
2387
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2388
2389
int diralphasort(const void *va, const void *vb)
{
2390
2391
2392
2393
    struct stat fileinfo;
    const char *a = *(char *const *)va, *b = *(char *const *)vb;
    int aisdir = stat(a, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
    int bisdir = stat(b, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
Chris Allegretta's avatar
Chris Allegretta committed
2394

2395
    if (aisdir != 0 && bisdir == 0)
2396
	return -1;
2397
    if (aisdir == 0 && bisdir != 0)
2398
	return 1;
2399

2400
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2401
2402
}

2403
/* Free our malloc()ed memory */
2404
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2405
{
2406
2407
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2408
2409
2410
    free(array);
}

2411
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2412
2413
void striponedir(char *foo)
{
2414
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2415

2416
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2417
    /* Don't strip the root dir */
2418
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2419
2420
	return;

2421
2422
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2423
    if (*tmp == '/')
2424
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2425
2426
2427
2428
2429

    while (*tmp != '/' && tmp != foo)
	tmp--;

    if (tmp != foo)
2430
2431
2432
2433
2434
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2435
    }
Chris Allegretta's avatar
Chris Allegretta committed
2436
2437
}

2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
int readable_dir(const char *path)
{
    DIR *dir = opendir(path);

    /* If dir is NULL, don't do closedir(), since that changes errno. */
    if (dir != NULL)
	closedir(dir);
    return dir != NULL;
}

2448
/* Initialize the browser code, including the list of files in *path */
2449
char **browser_init(const char *path, int *longest, int *numents)
2450
2451
2452
{
    DIR *dir;
    struct dirent *next;
2453
    char **filelist;
2454
    int i = 0;
2455
    size_t path_len;
2456
2457

    dir = opendir(path);
2458
    if (dir == NULL)
2459
2460
2461
2462
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
2463
	if (strcmp(next->d_name, ".") == 0)
2464
2465
2466
2467
2468
2469
2470
2471
	   continue;
	(*numents)++;
	if (strlen(next->d_name) > *longest)
	    *longest = strlen(next->d_name);
    }
    rewinddir(dir);
    *longest += 10;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2472
    filelist = (char **)nmalloc(*numents * sizeof (char *));
2473

2474
    if (strcmp(path, "/") == 0)
2475
2476
2477
	path = "";
    path_len = strlen(path);

2478
    while ((next = readdir(dir)) != NULL) {
2479
	if (strcmp(next->d_name, ".") == 0)
2480
2481
	   continue;

2482
2483
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2484
2485
	i++;
    }
2486
    closedir(dir);
2487
2488
2489
2490
2491
2492
2493

    if (*longest > COLS - 1)
	*longest = COLS - 1;

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2494
/* Our browser function.  inpath is the path to start browsing from */
2495
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2496
2497
2498
2499
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2500
2501
2502
    int numents = 0, i = 0, j = 0, longest = 0, abort = 0, col = 0;
    int selected = 0, editline = 0, width = 0, filecols = 0, lineno = 0;
    int kbinput = ERR;
2503
    bool meta_key, func_key;
2504
    char **filelist = (char **)NULL;
2505
#ifndef DISABLE_MOUSE
2506
2507
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2508

2509
2510
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2511
2512
2513
    /* If path isn't the same as inpath, we are being passed a new
	dir as an arg.  We free it here so it will be copied from 
	inpath below */
2514
    if (path != NULL && strcmp(path, inpath) != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2515
2516
2517
2518
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2519
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2520
    if (path == NULL)
2521
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2522
2523

    filelist = browser_init(path, &longest, &numents);
2524
    foo = charalloc(longest + 8);
Chris Allegretta's avatar
Chris Allegretta committed
2525

2526
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2527
2528
2529
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2530
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2531
2532
2533
2534
2535
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2536
2537

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2538
    do {
2539
2540
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2541

2542
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2543

2544
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2545
	currshortcut = browser_list;
2546
2547
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2548
2549
 	editline = 0;
	col = 0;
2550
	    
2551
	/* Compute line number we're on now, so we don't divide by zero later */
2552
2553
2554
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2555
2556

	switch (kbinput) {
2557

2558
#ifndef DISABLE_MOUSE
2559
	case KEY_MOUSE:
2560
	    if (getmouse(&mevent) == ERR)
2561
		return retval;
2562
2563
2564
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2565
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2566
2567
2568
2569
		int selectedbackup = selected;

		mevent.y -= 2;

2570
2571
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2572
		selected = (lineno / editwinrows) * editwinrows * width
2573
2574
2575
2576
2577
2578
			+ mevent.y * width + mevent.x / (longest + 2);

		/* If they clicked beyond the end of a row, select the
		 * end of that row. */
		if (mevent.x > width * (longest + 2))
		    selected--;
2579
2580
2581

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2582
		if (selected > numents - 1)
2583
		    selected = numents - 1;
2584
		else if (selectedbackup == selected)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2585
		    /* Put back the 'select' key */
2586
		    unget_kbinput('s', FALSE, FALSE);
2587
2588
	    } else {
		/* Must be clicking a shortcut */
2589
2590
2591
		int mouse_x, mouse_y;
		get_mouseinput(&mouse_x, &mouse_y, TRUE);
	    }
2592

2593
2594
            break;
#endif
2595
	case NANO_PREVLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2596
2597
2598
	    if (selected - width >= 0)
		selected -= width;
	    break;
2599
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2600
2601
2602
	    if (selected > 0)
		selected--;
	    break;
2603
	case NANO_NEXTLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2604
2605
2606
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
2607
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2608
2609
2610
2611
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2612
	case NANO_PREVPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2613
	case '-': /* Pico compatibility */
2614
	    if (selected >= (editwinrows + lineno % editwinrows) * width)
2615
		selected -= (editwinrows + lineno % editwinrows) * width;
Chris Allegretta's avatar
Chris Allegretta committed
2616
2617
2618
2619
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2620
	case NANO_NEXTPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2621
	case ' ': /* Pico compatibility */
2622
2623
	    selected += (editwinrows - lineno % editwinrows) * width;
	    if (selected >= numents)
Chris Allegretta's avatar
Chris Allegretta committed
2624
2625
		selected = numents - 1;
	    break;
2626
2627
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2628
	case '?': /* Pico compatibility */
2629
#ifndef DISABLE_HELP
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2630
	    do_help();
2631
	    curs_set(0);
2632
2633
2634
#else
	    nano_disabled_msg();
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2635
	    break;
2636
	case NANO_ENTER_KEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2637
2638
	case 'S': /* Pico compatibility */
	case 's':
Chris Allegretta's avatar
Chris Allegretta committed
2639
	    /* You can't cd up from / */
2640
2641
	    if (strcmp(filelist[selected], "/..") == 0 &&
		strcmp(path, "/") == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2642
		statusbar(_("Can't move up a directory"));
2643
		beep();
Rocco Corsi's avatar
   
Rocco Corsi committed
2644
2645
2646
		break;
	    }

2647
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2648
2649
2650
	    /* Note: the selected file can be outside the operating
	     * directory if it is .. or if it is a symlink to 
	     * directory outside the operating directory. */
2651
	    if (check_operating_dir(filelist[selected], FALSE) != 0) {
2652
2653
		statusbar(_("Can't go outside of %s in restricted mode"),
			operating_dir);
2654
2655
		beep();
		break;
2656
2657
2658
	    }
#endif

2659
	    if (stat(filelist[selected], &st) == -1) {
2660
2661
		statusbar(_("Can't open \"%s\": %s"), filelist[selected],
			strerror(errno));
2662
2663
2664
		beep();
		break;
	    }
2665

2666
2667
2668
2669
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2670
2671
	    }

2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
	    new_path = mallocstrcpy(NULL, filelist[selected]);

	    if (strcmp("..", tail(new_path)) == 0) {
		/* They want to go up a level, so strip off .. and the
		   current dir */
		striponedir(new_path);
		/* SPK for '.' path, get the current path via getcwd */
		if (strcmp(new_path, ".") == 0) {
		    free(new_path);
		    new_path = getcwd(NULL, PATH_MAX + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2682
		}
2683
2684
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2685

2686
2687
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
2688
2689
		statusbar(_("Can't open \"%s\": %s"), new_path,
			strerror(errno));
2690
2691
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2692
	    }
2693
2694
2695
2696
2697
2698
2699

	    free_charptrarray(filelist, numents);
	    free(foo);
	    free(path);
	    path = new_path;
	    return do_browser(path);

2700
	/* Go to a specific directory */
2701
2702
	case NANO_GOTOLINE_KEY:
	case NANO_GOTOLINE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2703
2704
	case 'G': /* Pico compatibility */
	case 'g':
Rocco Corsi's avatar
   
Rocco Corsi committed
2705
	    curs_set(1);
2706
	    j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2707
#ifndef NANO_SMALL
2708
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2709
#endif
2710
		_("Go To Directory"));
2711
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2712
2713
2714
	    curs_set(0);

	    if (j < 0) {
2715
		statusbar(_("Cancelled"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2716
2717
2718
		break;
	    }

2719
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2720

2721
2722
2723
	    if (new_path[0] != '/') {
		new_path = charealloc(new_path, strlen(path) + strlen(answer) + 2);
		sprintf(new_path, "%s/%s", path, answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2724
2725
	    }

2726
#ifndef DISABLE_OPERATINGDIR
2727
	    if (check_operating_dir(new_path, FALSE) != 0) {
2728
2729
2730
2731
2732
2733
2734
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		free(new_path);
		break;
	    }
#endif

	    if (!readable_dir(new_path)) {
Rocco Corsi's avatar
   
Rocco Corsi committed
2735
2736
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2737
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2738
		break;
2739
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2740
2741

	    /* Start over again with the new path value */
2742
2743
	    free_charptrarray(filelist, numents);
	    free(foo);
2744
2745
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2746
2747
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2748
	/* Stuff we want to abort the browser */
2749
	case NANO_CANCEL_KEY:
2750
	case NANO_EXIT_KEY:
2751
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2752
2753
	case 'E': /* Pico compatibility */
	case 'e':
2754
2755
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2756
2757
2758
2759
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2760
2761
	blank_edit();

2762
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
	    i = width * editwinrows * ((selected / width) / editwinrows);
	else
	    i = 0;

	wmove(edit, 0, 0);
	for (j = i; j < numents && editline <= editwinrows - 1; j++) {
	    filecols++;

	    strncpy(foo, tail(filelist[j]), strlen(tail(filelist[j])) + 1);
	    while (strlen(foo) < longest)
		strcat(foo, " ");
	    col += strlen(foo);

	    /* Put file info in the string also */
2777
2778
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2779
2780
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2781
2782
2783
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2784
2785
2786
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
2787
		    stat(filelist[j], &st);
2788
2789
2790
2791
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2792
2793
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2794
			(int) st.st_size);
2795
2796
2797
2798
2799
2800
		else if (st.st_size >= (1 << 30)) /* at least 1 gig */
		    sprintf(foo + longest - 7, "%4d GB", 
			(int) st.st_size >> 30);
		else if (st.st_size >= (1 << 20)) /* at least 1 meg */
		    sprintf(foo + longest - 7, "%4d MB", 
			(int) st.st_size >>     20);
2801
		else /* It's more than 1 k and less than a meg */
2802
2803
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2804
2805
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2806
	    /* Highlight the currently selected file/dir */
2807
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2808
		wattron(edit, A_REVERSE);
2809
2810
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2811
2812
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2813
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2814
2815
2816
2817
2818
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2819
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2820
2821
2822
2823
2824
2825
2826
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
2827
	wrefresh(edit);
2828
2829
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
Chris Allegretta's avatar
Chris Allegretta committed
2830
2831
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2832
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2833
2834
    edit_refresh();

Chris Allegretta's avatar
Chris Allegretta committed
2835
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2836
2837
2838
2839
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2840

2841
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2842
 starts do_browser from there, else from the current dir */
2843
char *do_browse_from(const char *inpath)
2844
2845
{
    struct stat st;
2846
    char *bob;
2847
2848
2849
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2850

2851
2852
2853
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2854

2855
2856
2857
2858
2859
2860
2861
2862
    /*
     * Perhaps path is a directory.  If so, we will pass that to
     * do_browser.  Otherwise, perhaps path is a directory / a file.  So
     * we try stripping off the last path element.  If it still isn't a
     * directory, just use the current directory. */

    if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	striponedir(path);
2863
2864
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2865
	    path = getcwd(NULL, PATH_MAX + 1);
2866
	}
2867
    }
2868

2869
2870
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2871
    if (check_operating_dir(path, FALSE) != 0)
2872
2873
2874
2875
2876
2877
2878
2879
2880
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2881
    return bob;
2882
}
Chris Allegretta's avatar
Chris Allegretta committed
2883
#endif /* !DISABLE_BROWSER */
2884

2885
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2886
2887
2888
/* Return $HOME/.nano_history, or NULL if we can't find the homedir.
 * The string is dynamically allocated, and should be freed. */
char *histfilename(void)
2889
{
2890
    char *nanohist = NULL;
2891

2892
2893
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2894

2895
2896
2897
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2898
    }
2899
2900
2901
2902
2903
2904
    return nanohist;
}

void load_history(void)
{
    char *nanohist = histfilename();
Chris Allegretta's avatar
Chris Allegretta committed
2905

2906
    /* assume do_rcfile() has reported missing home dir */
2907
2908
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2909

2910
	if (hist == NULL) {
2911
	    if (errno != ENOENT) {
2912
2913
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2914
		rcfile_error(N_("Error reading %s: %s"), nanohist, strerror(errno));
2915
2916
2917
		fprintf(stderr, _("\nPress Return to continue starting nano\n"));
		while (getchar() != '\n')
		    ;
2918
	    }
2919
	} else {
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
	    historyheadtype *history = &search_history;
	    char *line = NULL;
	    size_t buflen = 0;
	    ssize_t read;

	    while ((read = getline(&line, &buflen, hist)) >= 0) {
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
		    update_history(history, line);
		} else
2934
2935
2936
		    history = &replace_history;
	    }
	    fclose(hist);
2937
	    free(line);
2938
2939
	    UNSET(HISTORY_CHANGED);
	}
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
	free(nanohist);
    }
}

bool writehist(FILE *hist, historyheadtype *histhead)
{
    historytype *h;

    /* write oldest first */
    for (h = histhead->tail; h->prev != NULL; h = h->prev) {
	size_t len = strlen(h->data);

	sunder(h->data);
	if (fwrite(h->data, sizeof(char), len, hist) < len ||
		putc('\n', hist) == EOF)
	    return FALSE;
2956
    }
2957
    return TRUE;
2958
2959
2960
2961
2962
}

/* save histories to ~/.nano_history */
void save_history(void)
{
2963
    char *nanohist;
2964
2965

    /* don't save unchanged or empty histories */
2966
    if ((search_history.count == 0 && replace_history.count == 0) ||
2967
	!ISSET(HISTORY_CHANGED) || ISSET(VIEW_MODE))
2968
2969
	return;

2970
2971
2972
2973
    nanohist = histfilename();

    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "wb");
Chris Allegretta's avatar
Chris Allegretta committed
2974

2975
	if (hist == NULL)
2976
	    rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2977
	else {
2978
2979
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2980
2981
2982
2983

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2984
		rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2985
2986
2987
2988
2989
	    fclose(hist);
	}
	free(nanohist);
    }
}
2990
#endif /* !NANO_SMALL && ENABLE_NANORC */