files.c 76.2 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-2005 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
 *   any later version.                                                   *
 *                                                                        *
11
12
13
14
 *   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.                             *
Chris Allegretta's avatar
Chris Allegretta committed
15
16
17
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
18
19
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
Chris Allegretta's avatar
Chris Allegretta committed
20
21
22
 *                                                                        *
 **************************************************************************/

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

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

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

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

55
56
#ifdef ENABLE_COLOR
    update_color();
57
    if (!ISSET(NO_COLOR_SYNTAX))
58
	edit_refresh();
59
#endif
Chris Allegretta's avatar
Chris Allegretta committed
60
61
}

62
63
64
65
66
67
/* 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
68
{
69
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
70

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

75
76
77
    assert(strlen(buf) == len);

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

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

86
87
88
    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
89
90
91
	fileptr->prev = NULL;
	fileptr->next = fileage;
	fileptr->lineno = 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
92
	if (*first_line_ins == TRUE) {
93
	    *first_line_ins = FALSE;
94
	    /* If we're inserting into the first line of the file, then
95
96
	     * we want to make sure that our edit buffer stays on the
	     * first line and that fileage stays up to date. */
97
98
99
	    edittop = fileptr;
	} else
	    filebot = fileptr;
Chris Allegretta's avatar
Chris Allegretta committed
100
	fileage = fileptr;
101
102
    } else {
	assert(prev != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
103

Chris Allegretta's avatar
Chris Allegretta committed
104
105
106
107
108
109
110
111
112
	fileptr->prev = prev;
	fileptr->next = NULL;
	fileptr->lineno = prev->lineno + 1;
	prev->next = fileptr;
    }

    return fileptr;
}

113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/* 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
129
{
130
131
    size_t num_lines = 0;
	/* The number of lines in the file. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
132
133
    size_t num_chars;
	/* The number of characters in the file. */
134
135
136
137
    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. */
138
    size_t bufx = MAX_BUF_SIZE;
139
140
141
	/* 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
142
    char *buf;
143
144
145
146
147
148
149
150
	/* 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. */
151
#ifndef NANO_SMALL
152
    int format = 0;
153
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
154
#endif
Chris Allegretta's avatar
Chris Allegretta committed
155

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

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

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

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

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

180
181
	/* If it's a *nix file (LF) or a DOS file (CR LF), and file
	 * conversion isn't disabled, handle it! */
182
	if (input == '\n') {
183
#ifndef NANO_SMALL
184
185
	    /* 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
186
187
	     * currently think it's a Mac file. */
	    if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r' &&
188
189
		(format == 0 || format == 2))
		format++;
190
191
192
193
#endif

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

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

199
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
200
	    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
201
	    i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
202
#ifndef NANO_SMALL
203
204
	/* 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
205
206
	} else if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r') {

207
208
209
210
211
	    /* 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;
212
213
214

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

216
217
218
	    /* 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. */
219
	    len = 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
220

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

231
232
233
234
235
	    /* Now we allocate a bigger buffer MAX_BUF_SIZE characters
	     * at a time.  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
236
	    if (i >= bufx - 1) {
237
		bufx += MAX_BUF_SIZE;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
238
		buf = charealloc(buf, bufx);
Chris Allegretta's avatar
Chris Allegretta committed
239
	    }
240

241
	    buf[i] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
242
	    buf[i + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
243
244
	    i++;
	}
245
246
    }

247
    /* Perhaps this could use some better handling. */
248
249
250
    if (ferror(f))
	nperror(filename);
    fclose(f);
Chris Allegretta's avatar
Chris Allegretta committed
251

252
#ifndef NANO_SMALL
253
254
255
    /* 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') {
256
257
	len = 1;

258
259
	buf[0] = input;
	buf[1] = '\0';
260
261
    }
#endif
262

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

275
276
277
278
	/* Read in the last line properly. */
	fileptr = read_line(buf, fileptr, &first_line_ins, len);
	num_lines++;
    }
279

280
281
282
283
    free(buf);

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

287
    /* Did we try to insert a file of 0 bytes? */
288
289
290
291
292
293
294
295
296
297
    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();
298
	    totsize--;
299
	}
Chris Allegretta's avatar
Chris Allegretta committed
300
    }
301

302
303
304
    get_totals(fileage, filebot, NULL, &num_chars);
    totsize += num_chars;

305
#ifndef NANO_SMALL
306
    if (format == 3)
307
308
309
310
	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);
311
312
    else if (format == 2) {
	fmt = MAC_FILE;
313
314
315
	statusbar(P_("Read %lu line (Converted from Mac format)",
		"Read %lu lines (Converted from Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
316
317
    } else if (format == 1) {
	fmt = DOS_FILE;
318
319
320
	statusbar(P_("Read %lu line (Converted from DOS format)",
		"Read %lu lines (Converted from DOS format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
321
    } else
322
#endif
323
	statusbar(P_("Read %lu line", "Read %lu lines",
324
		(unsigned long)num_lines), (unsigned long)num_lines);
325

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

329
330
331
332
333
334
335
336
/* 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
337
338
339
340
{
    int fd;
    struct stat fileinfo;

341
    assert(f != NULL);
342

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

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

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

385
    buf = charalloc(namelen + digits(ULONG_MAX) + 7);
386
    strcpy(buf, name);
387
388
    strcpy(buf + namelen, ".save");
    namelen += 5;
389

390
    while (TRUE) {
391
	struct stat fs;
392
393

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

	i++;
399
	sprintf(buf + namelen, ".%lu", i);
400
401
    }

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

405
406
407
    return buf;
}

408
409
#ifndef NANO_SMALL
void execute_command(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
410
{
411
412
413
414
415
416
417
#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);
418
    }
419
420
421
422
423
424
425
426
427
#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
428

429
430
431
432
/* 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
433
    bool new_buffer = (fileage == NULL
434
435
#ifdef ENABLE_MULTIBUFFER
	 || ISSET(MULTIBUFFER)
436
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
437
	);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
438
439
440
441
	/* 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. */
442
443
444
445
    FILE *f;
    int rc;
	/* rc == -2 means that the statusbar displayed "New File".  -1
	 * means that the open failed.  0 means success. */
446

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

455
456
457
#ifdef ENABLE_MULTIBUFFER
    /* Update the current entry in the open_files structure. */
    add_open_file(TRUE);
458
459
#endif

460
461
462
463
464
    rc = open_file(name, new_buffer, &f);

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

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

    if (rc == 0) {
477
478
	file_format fmt_save = fmt;

479
	read_file(f, filename);
480
481
482
483
484
485

	/* 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
486
#ifndef NANO_SMALL
487
	stat(filename, &originalfilestat);
Chris Allegretta's avatar
Chris Allegretta committed
488
#endif
489
490
491
492
493
494
495
496
    }

    /* 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();
}

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

514
#ifndef DISABLE_WRAPPING
515
    wrap_reset();
516
#endif
517

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

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

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

566
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
567

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

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

581
582
		if (tmp == NULL)
		    continue;
583

584
585
		free(answer);
		answer = tmp;
586
587
588
589

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

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

600
601
	    /* If we don't have a file yet, go back to the statusbar
	     * prompt. */
602
603
604
605
606
	    if (i != 0
#ifdef ENABLE_MULTIBUFFER
		&& (i != -2 || !ISSET(MULTIBUFFER))
#endif
		)
607
608
		continue;

609
#ifdef ENABLE_MULTIBUFFER
610
	    if (!ISSET(MULTIBUFFER)) {
611
#endif
612
613
		/* If we're not inserting into a new buffer, partition
		 * the filestruct so that it contains no text and hence
614
615
616
		 * looks like a new buffer, and keep track of whether
		 * the top of the partition is the top of the edit
		 * window. */
617
		filepart = partition_filestruct(current, current_x,
618
			current, current_x);
619
		at_edittop = (fileage == edittop);
620
#ifdef ENABLE_MULTIBUFFER
621
	    }
622
623
#endif

624
625
626
627
628
629
#ifndef NANO_SMALL
	    if (execute)
		execute_command(answer);
	    else {
#endif
		answer = mallocstrassn(answer, real_dir_from_tilde(answer));
630
		load_buffer(answer);
631
632
633
#ifndef NANO_SMALL
	    }
#endif
634
635

#ifdef ENABLE_MULTIBUFFER
636
	    if (!ISSET(MULTIBUFFER))
637
#endif
638
639
	    {
		filestruct *top_save = fileage;
640

641
642
643
644
645
646
647
648
649
		/* 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
650
		/* If we didn't insert into a new buffer, unpartition
651
652
653
654
		 * 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. */
655
		unpartition_filestruct(&filepart);
656

657
658
659
		/* Renumber starting with the beginning line of the old
		 * partition. */
		renumber(top_save);
660

661
662
		/* Set edittop back to what it was before. */
		edittop = edittop_save;
663
	    }
664

665
#ifdef ENABLE_MULTIBUFFER
666
667
668
	    if (ISSET(MULTIBUFFER)) {
		/* Update the titlebar. */
		titlebar(NULL);
669

670
671
672
		/* Reinitialize the shortcut list. */
		shortcut_init(FALSE);
	    } else {
673
#endif
674
675
		/* Mark the file as modified. */
		set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
676

677
		/* Restore the old place we want. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
678
		placewewant = pww_save;
679
#ifdef ENABLE_MULTIBUFFER
680
	    }
Chris Allegretta's avatar
Chris Allegretta committed
681
682
#endif

683
684
685
686
687
	    /* Refresh the screen. */
	    edit_refresh();

	    break;
	}
688
    }
Chris Allegretta's avatar
Chris Allegretta committed
689

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
690
    free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
691
692
}

693
void do_insertfile_void(void)
694
{
695
#ifdef ENABLE_MULTIBUFFER
696
697
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
	statusbar(_("Key illegal in non-multibuffer mode"));
698
    else
699
#endif
700
701
702
703
704
	do_insertfile(
#ifndef NANO_SMALL
		FALSE
#endif
		);
705
706
707
708

    display_main_list();
}

709
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
710
/* Create a new openfilestruct node. */
711
openfilestruct *make_new_opennode(void)
Chris Allegretta's avatar
Chris Allegretta committed
712
{
713
714
    openfilestruct *newnode =
	(openfilestruct *)nmalloc(sizeof(openfilestruct));
Chris Allegretta's avatar
Chris Allegretta committed
715
716
717
718
719
720
    newnode->filename = NULL;
    return newnode;
}

/* Splice a node into an existing openfilestruct. */
void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
721
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
722
{
723
    assert(newnode != NULL && begin != NULL);
724

Chris Allegretta's avatar
Chris Allegretta committed
725
726
727
728
729
730
731
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

732
733
/* Unlink a node from the rest of the openfilestruct, and delete it. */
void unlink_opennode(openfilestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
734
{
735
    assert(fileptr != NULL && fileptr->prev != NULL && fileptr->next != NULL && fileptr != fileptr->prev && fileptr != fileptr->next);
736

737
738
739
    fileptr->prev->next = fileptr->next;
    fileptr->next->prev = fileptr->prev;
    delete_opennode(fileptr);
Chris Allegretta's avatar
Chris Allegretta committed
740
741
742
743
744
}

/* Delete a node from the openfilestruct. */
void delete_opennode(openfilestruct *fileptr)
{
745
    assert(fileptr != NULL && fileptr->filename != NULL && fileptr->fileage != NULL);
746

747
748
749
    free(fileptr->filename);
    free_filestruct(fileptr->fileage);
    free(fileptr);
Chris Allegretta's avatar
Chris Allegretta committed
750
751
}

752
753
754
#ifdef DEBUG
/* Deallocate all memory associated with this and later files, including
 * the lines of text. */
Chris Allegretta's avatar
Chris Allegretta committed
755
756
void free_openfilestruct(openfilestruct *src)
{
757
    assert(src != NULL);
758

759
760
761
    while (src != src->next) {
	src = src->next;
	delete_opennode(src->prev);
Chris Allegretta's avatar
Chris Allegretta committed
762
    }
763
    delete_opennode(src);
Chris Allegretta's avatar
Chris Allegretta committed
764
}
765
#endif
Chris Allegretta's avatar
Chris Allegretta committed
766

767
/* Add/update an entry to the open_files openfilestruct.  If update is
768
 * FALSE, a new entry is created; otherwise, the current entry is
769
 * updated. */
770
void add_open_file(bool update)
771
{
772
    if (open_files == NULL && update)
773
	return;
774

775
776
777
778
779
780
781
782
783
    /* 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);
784
785
786
	open_files = open_files->next;
    }

787
    /* Save the current filename. */
788
    open_files->filename = mallocstrcpy(open_files->filename, filename);
789

790
#ifndef NANO_SMALL
791
    /* Save the current file's stat. */
792
793
794
    open_files->originalfilestat = originalfilestat;
#endif

795
796
797
798
799
800
801
802
803
    /* 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;
804

805
806
    /* Save the current cursor position. */
    open_files->current_x = current_x;
807

808
809
    /* Save the current place we want. */
    open_files->placewewant = placewewant;
810

811
812
    /* Save the current total number of lines. */
    open_files->totlines = totlines;
813

814
815
    /* Save the current total size. */
    open_files->totsize = totsize;
816

817
818
    /* Start with no flags saved. */
    open_files->flags = 0;
819

820
821
822
    /* Save the current modification status. */
    if (ISSET(MODIFIED))
	open_files->flags |= MODIFIED;
823

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
824
#ifndef NANO_SMALL
825
826
827
828
829
    /* 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
830
831
    }

832
833
834
    /* Save the current file format. */
    open_files->fmt = fmt;
#endif
835
836

#ifdef DEBUG
837
    fprintf(stderr, "filename is %s\n", open_files->filename);
838
839
840
#endif
}

841
/* Read the current entry in the open_files structure and set up the
842
 * currently open file buffer using that entry's information. */
843
void load_open_file(void)
844
{
845
    assert(open_files != NULL);
846

847
    /* Restore the current filename. */
848
    filename = mallocstrcpy(filename, open_files->filename);
849

850
#ifndef NANO_SMALL
851
    /* Restore the current file's stat. */
852
853
    originalfilestat = open_files->originalfilestat;
#endif
854
855

    /* Restore the current file buffer. */
856
    fileage = open_files->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
857
    filebot = open_files->filebot;
858

859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
    /* 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
879
880
881
882
883
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

#ifndef NANO_SMALL
884
885
886
887
    /* 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
888
889
890
	SET(MARK_ISSET);
    } else
	UNSET(MARK_ISSET);
891

892
893
    /* Restore the current file format. */
    fmt = open_files->fmt;
Chris Allegretta's avatar
Chris Allegretta committed
894
#endif
895

Chris Allegretta's avatar
Chris Allegretta committed
896
897
898
#ifdef ENABLE_COLOR
    update_color();
#endif
899
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
900

901
    /* Update the titlebar. */
902
903
904
    titlebar(NULL);
}

905
/* Open either the next or previous file buffer. */
906
void open_prevnext_file(bool next)
907
{
908
    add_open_file(TRUE);
909

910
    assert(open_files != NULL);
911

912
913
    /* If only one file buffer is open, indicate it on the statusbar and
     * get out. */
914
    if (open_files == open_files->next) {
915
	statusbar(_("No more open file buffers"));
916
	return;
917
918
    }

919
920
921
    /* Switch to the next or previous file, depending on the value of
     * next. */
    open_files = next ? open_files->next : open_files->prev;
922
923

#ifdef DEBUG
924
    fprintf(stderr, "filename is %s\n", open_files->filename);
925
926
#endif

927
    /* Load the file we switched to. */
928
929
    load_open_file();

930
    /* And indicate the switch on the statusbar. */
931
    statusbar(_("Switched to %s"),
932
      ((open_files->filename[0] == '\0') ? _("New Buffer") :
933
	open_files->filename));
934

935
936
937
938
939
#ifdef DEBUG
    dump_buffer(current);
#endif
}

940
941
/* Open the previous entry in the open_files structure.  This function
 * is used by the shortcut list. */
942
void open_prevfile_void(void)
943
{
944
    open_prevnext_file(FALSE);
945
946
}

947
948
/* Open the next entry in the open_files structure.  This function is
 * used by the shortcut list. */
949
void open_nextfile_void(void)
950
{
951
    open_prevnext_file(TRUE);
952
953
}

954
955
/* 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
956
 * there are no more open file buffers. */
957
bool close_open_file(void)
958
{
959
    assert(open_files != NULL);
960

961
962
    /* If only one file is open, get out. */
    if (open_files == open_files->next)
963
	return FALSE;
964

965
966
    /* Open the next file. */
    open_nextfile_void();
Chris Allegretta's avatar
Chris Allegretta committed
967

968
969
    /* Close the file we had open before. */
    unlink_opennode(open_files->prev);
970

971
    /* Reinitialize the shortcut list. */
972
    shortcut_init(FALSE);
973
    display_main_list();
974

975
    return TRUE;
976
}
977
#endif /* ENABLE_MULTIBUFFER */
978

979
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
980
/* When passed "[relative path]" or "[relative path][filename]" in
981
 * origpath, return "[full path]" or "[full path][filename]" on success,
982
983
984
985
 * or NULL on error.  Do this if the file doesn't exist but the relative
 * path does, since the file could exist in memory but not yet on disk).
 * Don't do this if the relative path doesn't exist, since we won't be
 * able to go there. */
986
char *get_full_path(const char *origpath)
987
{
988
    char *d_here, *d_there = NULL;
989

990
991
    if (origpath == NULL)
    	return NULL;
992

993
994
995
    /* Get the current directory. */
    d_here = charalloc(PATH_MAX + 1);
    d_here = getcwd(d_here, PATH_MAX + 1);
996

997
    if (d_here != NULL) {
998
999
1000
1001
	const char *last_slash;
	char *d_there_file = NULL;
	bool path_only;
	struct stat fileinfo;
1002

1003
1004
	align(&d_here);

1005
1006
	/* If the current directory isn't "/", tack a slash onto the end
	 * of it. */
1007
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1008
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1009
1010
	    strcat(d_here, "/");
	}
1011

1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
	d_there = real_dir_from_tilde(origpath);

	assert(d_there != NULL);

	/* Stat d_there.  If stat() fails, assume that d_there refers to
	 * a new file that hasn't been saved to disk yet.  Set path_only
	 * to TRUE if d_there refers to a directory, and FALSE if
	 * d_there refers to a file. */
	path_only = !stat(d_there, &fileinfo) &&
		S_ISDIR(fileinfo.st_mode);

	/* If path_only is TRUE, make sure d_there ends in a slash. */
1024
	if (path_only) {
1025
1026
1027
1028
	    size_t d_there_len = strlen(d_there);

	    if (d_there[d_there_len - 1] != '/') {
		d_there = charealloc(d_there, d_there_len + 2);
1029
1030
1031
1032
		strcat(d_there, "/");
	    }
	}

1033
	/* Search for the last slash in d_there. */
1034
1035
	last_slash = strrchr(d_there, '/');

1036
1037
1038
1039
	/* If we didn't find one, then make sure the answer is in the
	 * format "d_here/d_there". */
	if (last_slash == NULL) {
	    assert(!path_only);
1040

1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
	    d_there_file = d_there;
	    d_there = d_here;
	} else {
	    /* If path_only is FALSE, then save the filename portion of
	     * the answer, everything after the last slash, in
	     * d_there_file. */
	    if (!path_only)
		d_there_file = mallocstrcpy(NULL, last_slash + 1);

	    /* And remove the filename portion of the answer from
	     * d_there. */
	    null_at(&d_there, last_slash - d_there + 1);

	    /* Go to the path specified in d_there. */
	    if (chdir(d_there) == -1) {
1056
		free(d_there);
1057
1058
1059
1060
		d_there = NULL;
	    } else {
		/* Get the full path and save it in d_there. */
		free(d_there);
1061

1062
1063
1064
		d_there = charalloc(PATH_MAX + 1);
		d_there = getcwd(d_there, PATH_MAX + 1);

1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
		if (d_there != NULL) {
		    align(&d_there);

		    if (strcmp(d_there, "/") != 0) {
			/* Make sure d_there ends in a slash. */
			d_there = charealloc(d_there,
				strlen(d_there) + 2);
			strcat(d_there, "/");
		    }
		} else
1075
1076
1077
1078
1079
1080
1081
1082
		    /* If we couldn't get the full path, set path_only
		     * to TRUE so that we clean up correctly, free all
		     * allocated memory, and return NULL. */
		    path_only = TRUE;

		/* Finally, go back to the path specified in d_here,
		 * where we were before. */
		chdir(d_here);
1083
1084
	    }

1085
1086
	    /* Free d_here, since we're done using it. */
	    free(d_here);
1087
	}
1088

1089
1090
1091
1092
1093
	/* At this point, if path_only is FALSE and d_there exists,
	 * d_there contains the path portion of the answer and
	 * d_there_file contains the filename portion of the answer.  If
	 * this is the case, tack d_there_file onto the end of
	 * d_there, so that d_there contains the complete answer. */
1094
	if (!path_only && d_there != NULL) {
1095
1096
1097
1098
1099
1100
	    d_there = charealloc(d_there, strlen(d_there) +
		strlen(d_there_file) + 1);
	    strcat(d_there, d_there_file);
 	}

	/* Free d_there_file, since we're done using it. */
1101
1102
1103
	free(d_there_file);
    }

1104
    return d_there;
1105
}
1106
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1107
1108

#ifndef DISABLE_SPELLER
1109
1110
1111
/* Return the full version of path, as returned by get_full_path().  On
 * error, if path doesn't reference a directory, or if the directory
 * isn't writable, return NULL. */
1112
char *check_writable_directory(const char *path)
1113
{
1114
1115
    char *full_path = get_full_path(path);

1116
    /* If get_full_path() fails, return NULL. */
1117
    if (full_path == NULL)
1118
	return NULL;
1119

1120
1121
1122
1123
    /* If we can't write to path or path isn't a directory, return
     * NULL. */
    if (access(full_path, W_OK) != 0 ||
		full_path[strlen(full_path) - 1] != '/') {
1124
	free(full_path);
1125
	return NULL;
1126
    }
1127
1128
1129
1130
1131

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

1132
1133
1134
1135
/* This function acts like a call to tempnam(NULL, "nano.").  The
 * difference is that the number of calls is not limited by TMP_MAX.
 * Instead we use mkstemp(). */
char *safe_tempnam(void)
1136
{
1137
1138
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1139
    int filedesc;
1140

1141
1142
1143
    /* 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, leave full_tempdir set to NULL. */
1144
    TMPDIR_env = getenv("TMPDIR");
1145
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1146
	full_tempdir = check_writable_directory(TMPDIR_env);
1147

1148
1149
    /* If $TMPDIR is unset, empty, or not a writable directory, and
     * full_tempdir is NULL, try P_tmpdir instead. */
1150
    if (full_tempdir == NULL)
1151
	full_tempdir = check_writable_directory(P_tmpdir);
1152

1153
1154
1155
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1156

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1157
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1158
    strcat(full_tempdir, "nano.XXXXXX");
1159
1160
    filedesc = mkstemp(full_tempdir);

1161
1162
    /* If mkstemp() succeeded, close the resulting file, delete it
     * (since it'll be 0 bytes long), and return the filename. */
1163
    if (filedesc != -1) {
1164
	close(filedesc);
1165
1166
	unlink(full_tempdir);
	return full_tempdir;
1167
    }
1168
1169

    free(full_tempdir);
1170

1171
    return NULL;
1172
1173
}
#endif /* !DISABLE_SPELLER */
1174
1175

#ifndef DISABLE_OPERATINGDIR
1176
1177
1178
1179
1180
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1181
    if (operating_dir == NULL)
1182
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1183

1184
1185
1186
    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
1187
     * inaccessible, unset operating_dir. */
1188
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1189
1190
1191
1192
1193
1194
1195
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1196
1197
1198
1199
1200
/* Check to see if we're inside the operating directory.  Return FALSE
 * if we are, or TRUE otherwise.  If allow_tabcomp is TRUE, allow
 * incomplete names that would be matches for the operating directory,
 * so that tab completion will work. */
bool check_operating_dir(const char *currpath, bool allow_tabcomp)
1201
{
1202
1203
1204
1205
    /* 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. */
1206

1207
    char *fullpath;
1208
    bool retval = FALSE;
1209
    const char *whereami1, *whereami2 = NULL;
1210

1211
    /* If no operating directory is set, don't bother doing anything. */
1212
    if (operating_dir == NULL)
1213
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1214

1215
    assert(full_operating_dir != NULL);
1216
1217

    fullpath = get_full_path(currpath);
1218
1219

    /* fullpath == NULL means some directory in the path doesn't exist
1220
     * or is unreadable.  If allow_tabcomp is FALSE, then currpath is
1221
1222
     * what the user typed somewhere.  We don't want to report a
     * non-existent directory as being outside the operating directory,
1223
     * so we return FALSE.  If allow_tabcomp is TRUE, then currpath
1224
1225
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1226
    if (fullpath == NULL)
1227
	return allow_tabcomp;
1228
1229
1230
1231
1232

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

1233
1234
1235
1236
1237
    /* 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. */
1238
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1239
1240
	retval = TRUE;
    free(fullpath);
1241
1242

    /* Otherwise, we're still inside it. */
1243
    return retval;
1244
}
1245
1246
#endif

1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
#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

1271
1272
1273
1274
1275
/* 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)
{
1276
    char buf[MAX_BUF_SIZE];
1277
1278
1279
1280
    size_t charsread;
    int retval = 0;

    assert(inn != NULL && out != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1281

1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
    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);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1293

1294
1295
1296
1297
    if (fclose(inn) == EOF)
	retval = -1;
    if (fclose(out) == EOF)
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1298

1299
1300
1301
    return retval;
}

1302
/* Write a file out.  If tmp is FALSE, we set the umask to disallow
1303
1304
1305
 * 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
1306
 *
1307
1308
 * 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.
1309
 *
1310
1311
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1312
 *
1313
1314
 * nonamechange means don't change the current filename.  It is ignored
 * if tmp is FALSE or if we're appending/prepending.
1315
1316
 *
 * Return -1 on error, 1 on success. */
1317
1318
int write_file(const char *name, bool tmp, int append, bool
	nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1319
{
1320
1321
    int retval = -1;
	/* Instead of returning in this function, you should always
1322
1323
1324
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
    const filestruct *fileptr = fileage;
1325
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1326
	/* The file descriptor we use. */
1327
    mode_t original_umask = 0;
1328
	/* Our umask, from when nano started. */
1329
    bool realexists;
1330
	/* The result of stat().  TRUE if the file exists, FALSE
1331
	 * otherwise.  If name is a link that points nowhere, realexists
1332
	 * is FALSE. */
1333
1334
    struct stat st;
	/* The status fields filled in by stat(). */
1335
    bool anyexists;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1336
	/* The result of lstat().  Same as realexists unless name is a
1337
1338
1339
1340
	 * link. */
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1341
	/* name after tilde expansion. */
1342
1343
1344
1345
    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
1346

1347
    assert(name != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1348

1349
    if (name[0] == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1350
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1351

1352
1353
    if (!tmp)
	titlebar(NULL);
1354

1355
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1356

1357
#ifndef DISABLE_OPERATINGDIR
1358
    /* If we're writing a temporary file, we're probably going outside
1359
     * the operating directory, so skip the operating directory test. */
1360
    if (!tmp && check_operating_dir(realname, FALSE)) {
1361
1362
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1363
1364
1365
    }
#endif

1366
    anyexists = (lstat(realname, &lst) != -1);
1367

1368
1369
1370
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
1371

1372
1373
1374
    /* 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)) {
1375
1376
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1377
1378
1379
	goto cleanup_and_exit;
    }

1380
    /* Save the state of file at the end of the symlink (if there is
1381
     * one). */
1382
    realexists = (stat(realname, &st) != -1);
1383

1384
1385
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1386
1387
1388
1389
     * 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. */
1390
    if (ISSET(BACKUP_FILE) && !tmp && realexists &&
1391
1392
1393
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1394
	FILE *backup_file;
1395
	char *backupname;
1396
	struct utimbuf filetime;
1397
	int copy_status;
1398

1399
	/* Save the original file's access and modification times. */
1400
1401
1402
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1403
	/* Open the original file to copy to the backup. */
1404
	f = fopen(realname, "rb");
1405

1406
	if (f == NULL) {
1407
	    statusbar(_("Error reading %s: %s"), realname,
1408
		strerror(errno));
1409
	    goto cleanup_and_exit;
1410
1411
	}

1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
	/* 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);
	}
1444

1445
1446
1447
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1448
	backup_file = fopen(backupname, "wb");
1449

1450
1451
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1452
1453
	    statusbar(_("Error writing %s: %s"), backupname,
		strerror(errno));
1454
	    free(backupname);
1455
1456
1457
1458
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1459
1460
1461
	}

#ifdef DEBUG
1462
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1463
1464
#endif

1465
1466
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1467

1468
	/* And set metadata. */
1469
1470
1471
1472
	if (copy_status != 0 ||
		chown(backupname, originalfilestat.st_uid,
		originalfilestat.st_gid) == -1 ||
		utime(backupname, &filetime) == -1) {
1473
1474
	    free(backupname);
	    if (copy_status == -1)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1475
1476
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1477
1478
1479
1480
1481
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1482
1483
	free(backupname);
    }
1484
#endif /* !NANO_SMALL */
1485

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1486
1487
    /* If NOFOLLOW_SYMLINKS is set and the file is a link, we aren't
     * doing prepend or append.  So we delete the link first, and just
1488
1489
1490
1491
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1492
	goto cleanup_and_exit;
1493
    }
1494

1495
1496
    original_umask = umask(0);
    umask(original_umask);
1497

1498
    /* If we create a temp file, we don't let anyone else access it.  We
1499
     * create a temp file if tmp is TRUE or if we're prepending. */
1500
1501
1502
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

1503
    /* If we're prepending, copy the file to a temp file. */
1504
1505
1506
1507
1508
1509
1510
1511
1512
    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;
1513

1514
1515
1516
1517
1518
	if (fd != -1) {
	    f = fdopen(fd, "wb");
	    if (f == NULL)
		close(fd);
	}
1519

1520
	if (f == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1521
1522
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1523
	    unlink(tempname);
1524
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1525
	}
1526

1527
	fd_source = open(realname, O_RDONLY | O_CREAT);
1528

1529
1530
1531
1532
1533
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
	}
1534

1535
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1536
1537
	    statusbar(_("Error reading %s: %s"), realname,
		strerror(errno));
1538
1539
1540
1541
1542
1543
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

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

1551
1552
    /* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This is
     * now copied from joe, because wiggy says so *shrug*. */
1553
    fd = open(realname, O_WRONLY | O_CREAT |
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1554
	((append == 1) ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
1555
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1556

1557
    /* Set the umask back to the user's original value. */
1558
1559
1560
1561
1562
    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));
1563

1564
1565
1566
	/* tempname has been set only if we're prepending. */
	if (tempname != NULL)
	    unlink(tempname);
1567
1568
	goto cleanup_and_exit;
    }
1569

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

1577
    /* There might not be a magicline.  There won't be when writing out
1578
1579
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1580

1581
1582
1583
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1584

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

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

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

1593
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1594
1595
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1596
	    fclose(f);
1597
	    goto cleanup_and_exit;
1598
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1599

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

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

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

1626
    /* If we're prepending, open the temp file, and append it to f. */
1627
    if (append == 2) {
1628
1629
1630
1631
1632
1633
1634
1635
	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);
1636
	}
1637
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1638
1639
	    statusbar(_("Error reading %s: %s"), tempname,
		strerror(errno));
1640
	    fclose(f);
1641
	    goto cleanup_and_exit;
1642
1643
	}

1644
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1645
1646
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1647
	    goto cleanup_and_exit;
1648
	}
1649
1650
1651
1652
    } 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
1653
    }
1654

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

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

    retval = 1;

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

1683
1684
1685
#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
1686
1687
1688
1689
 * 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. */
1690
int write_marked(const char *name, bool tmp, int append)
1691
1692
{
    int retval = -1;
1693
    bool old_modified = ISSET(MODIFIED);
1694
	/* write_file() unsets the MODIFIED flag. */
1695
1696
1697
1698
1699
1700
1701
1702
    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,
1703
	(const filestruct **)&bot, &bot_x, NULL);
1704
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1705
1706

    /* If the line at filebot is blank, treat it as the magicline and
1707
1708
1709
1710
1711
     * 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();
1712

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

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

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

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

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

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

1740
    currshortcut = writefile_list;
1741

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

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

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

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

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

1769
	if (ISSET(BACKUP_FILE))
1770
	   backupstr = N_(" [Backup]");
1771
1772
1773
	else
	   backupstr = "";

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

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

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

1810
#ifndef DISABLE_BROWSER
1811
1812
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
1813

1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
		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
1825
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1826
#ifndef NANO_SMALL
1827
	    if (i == TOGGLE_DOS_KEY) {
1828
		fmt = (fmt == DOS_FILE) ? NIX_FILE : DOS_FILE;
1829
1830
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1831
		fmt = (fmt == MAC_FILE) ? NIX_FILE : MAC_FILE;
1832
1833
1834
1835
1836
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1837
#endif /* !NANO_SMALL */
1838
1839
1840
1841
1842
1843
1844
	    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
1845

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

#ifdef NANO_EXTRA
1851
1852
1853
1854
1855
1856
1857
	    if (exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0 && !did_cred) {
		do_credits();
		did_cred = TRUE;
		retval = -1;
		break;
	    }
1858
#endif
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
	    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'
1872
#ifndef NANO_SMALL
1873
			&& (exiting || !ISSET(MARK_ISSET))
1874
#endif
1875
			) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1876
1877
		    i = do_yesno(FALSE,
			_("Save file under DIFFERENT NAME ? "));
1878
1879
1880
		    if (i == 0 || i == -1)
			continue;
		}
1881
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1882

1883
#ifndef NANO_SMALL
1884
1885
1886
1887
1888
1889
1890
	    /* 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
1891
#endif /* !NANO_SMALL */
1892
		retval = write_file(answer, FALSE, append, FALSE);
1893

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

	    break;
	}
1903
    } /* while (TRUE) */
1904
1905

    free(ans);
1906

1907
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1908
1909
}

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

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

1922
1923
1924
    if (buf == NULL)
    	return NULL;

1925
    if (buf[0] == '~') {
1926
	size_t i;
1927
	const char *tilde_dir;
1928

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

1933
1934
1935
1936
1937
1938
1939
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
	    tilde_dir = homedir;
	} else {
	    const struct passwd *userdata;

1940
1941
1942
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1943
		strncmp(userdata->pw_name, buf + 1, i - 1) != 0);
1944
1945
	    endpwent();
	    tilde_dir = userdata->pw_dir;
Chris Allegretta's avatar
Chris Allegretta committed
1946
	}
1947

1948
1949
1950
	if (tilde_dir != NULL) {
	    dirtmp = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	    sprintf(dirtmp, "%s%s", tilde_dir, buf + i);
1951
	}
1952
    }
1953

1954
1955
    /* Set a default value for dirtmp, in case the user's home directory
     * isn't found. */
1956
    if (dirtmp == NULL)
1957
	dirtmp = mallocstrcpy(NULL, buf);
1958

1959
    return dirtmp;
1960
1961
}

1962
#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
1963
1964
/* Our sort routine for file listings.  Sort alphabetically and
 * case-insensitively, and sort directories before filenames. */
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
int diralphasort(const void *va, const void *vb)
{
    struct stat fileinfo;
    const char *a = *(const char *const *)va;
    const char *b = *(const char *const *)vb;
    bool aisdir = stat(a, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
    bool bisdir = stat(b, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);

    if (aisdir && !bisdir)
	return -1;
    if (!aisdir && bisdir)
	return 1;

    return strcasecmp(a, b);
}
#endif

Chris Allegretta's avatar
Chris Allegretta committed
1982
#ifndef DISABLE_TABCOMP
1983
1984
/* Is the given file a directory? */
int is_dir(const char *buf)
1985
{
1986
    char *dirptr = real_dir_from_tilde(buf);
1987
1988
    struct stat fileinfo;

1989
1990
    int ret = (stat(dirptr, &fileinfo) != -1 &&
		S_ISDIR(fileinfo.st_mode));
1991

1992
    assert(buf != NULL && dirptr != buf);
1993

1994
    free(dirptr);
1995

1996
    return ret;
1997
}
Chris Allegretta's avatar
Chris Allegretta committed
1998

1999
/* These functions (username_tab_completion(), cwd_tab_completion(), and
Chris Allegretta's avatar
Chris Allegretta committed
2000
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
2001
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
 *
 * 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.
2014
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
2015

2016
2017
2018
2019
/* We consider the first buflen characters of buf for ~username tab
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
	size_t buflen)
Chris Allegretta's avatar
Chris Allegretta committed
2020
{
2021
2022
    char **matches = NULL;
    const struct passwd *userdata;
2023

2024
    assert(buf != NULL && num_matches != NULL && buflen > 0);
2025

2026
    *num_matches = 0;
2027

2028
    while ((userdata = getpwent()) != NULL) {
2029
2030
2031
	if (strncmp(userdata->pw_name, buf + 1, buflen - 1) == 0) {
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2032

2033
2034
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2035
2036
2037
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2038
2039
#endif

2040
2041
2042
2043
2044
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
	    matches[*num_matches] =
		charalloc(strlen(userdata->pw_name) + 2);
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2045
	    ++(*num_matches);
2046
	}
2047
2048
    }
    endpwent();
2049

2050
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2051
2052
}

2053
/* This was originally called exe_n_cwd_tab_completion(), but we're not
2054
2055
2056
 * worried about executables, only filenames :> */
char **cwd_tab_completion(const char *buf, size_t *num_matches, size_t
	buflen)
Chris Allegretta's avatar
Chris Allegretta committed
2057
{
2058
2059
2060
2061
2062
2063
2064
    char *dirname = mallocstrcpy(NULL, buf);
    char *filename;
#ifndef DISABLE_OPERATINGDIR
    size_t dirnamelen;
#endif
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2065
    DIR *dir;
2066
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2067

2068
    assert(dirname != NULL && num_matches != NULL && buflen >= 0);
2069

2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
    *num_matches = 0;
    null_at(&dirname, buflen);

    /* Okie, if there's a / in the buffer, strip out the directory
     * part. */
    filename = strrchr(dirname, '/');
    if (filename != NULL) {
	char *tmpdirname = filename + 1;

	filename = mallocstrcpy(NULL, tmpdirname);
	*tmpdirname = '\0';
	tmpdirname = dirname;
	dirname = real_dir_from_tilde(dirname);
	free(tmpdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2084
    } else {
2085
2086
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2087
2088
    }

2089
    assert(dirname[strlen(dirname) - 1] == '/');
2090

Chris Allegretta's avatar
Chris Allegretta committed
2091
    dir = opendir(dirname);
2092

2093
    if (dir == NULL) {
2094
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2095
	beep();
2096
2097
2098
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2099
    }
2100
2101
2102
2103
2104
2105

#ifndef DISABLE_OPERATINGDIR
    dirnamelen = strlen(dirname);
#endif
    filenamelen = strlen(filename);

2106
    while ((nextdir = readdir(dir)) != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2107
2108

#ifdef DEBUG
2109
	fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2110
#endif
2111
	/* See if this matches. */
2112
2113
2114
2115
	if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
		(*filename == '.' ||
		(strcmp(nextdir->d_name, ".") != 0 &&
		strcmp(nextdir->d_name, "..") != 0))) {
2116
2117
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2118
2119
2120

#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2121
2122
2123
2124
2125
	     * 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. */
	    char *tmp2 = charalloc(strlen(dirname) +
2126
		strlen(nextdir->d_name) + 1);
2127

2128
	    sprintf(tmp2, "%s%s", dirname, nextdir->d_name);
2129
2130
2131
	    if (check_operating_dir(tmp2, TRUE)) {
		free(tmp2);
		continue;
2132
	    }
2133
	    free(tmp2);
2134
2135
#endif

2136
2137
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
2138
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2139
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2140
2141
	}
    }
2142
2143
    closedir(dir);
    free(dirname);
2144
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2145

2146
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2147
2148
}

2149
2150
2151
/* Do tab completion.  This function now has an arg which refers to how
 * much the statusbar cursor position (place) should be advanced. */
char *input_tab(char *buf, size_t *place, bool *lastwastab, bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2152
{
2153
2154
    size_t num_matches = 0;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2155

2156
    assert(buf != NULL && place != NULL && *place <= strlen(buf) && lastwastab != NULL && list != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2157

2158
    *list = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2159

2160
2161
2162
2163
    /* If the word starts with `~' and there is no slash in the word,
     * then try completing this word as a username. */
    if (*place > 0 && *buf == '~') {
	const char *bob = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2164

2165
2166
2167
2168
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2169

2170
2171
2172
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
	matches = cwd_tab_completion(buf, &num_matches, *place);
2173

2174
2175
2176
2177
2178
    if (num_matches <= 0)
	beep();
    else {
	size_t match, common_len = 0;
	char *mzero;
2179
2180
2181
	const char *lastslash = revstrstr(buf, "/", buf + *place);
	size_t lastslash_len = (lastslash == NULL) ? 0 :
		lastslash - buf + 1;
2182
2183
2184
2185
2186
2187
2188

	while (TRUE) {
	    for (match = 1; match < num_matches; match++) {
		if (matches[0][common_len] !=
			matches[match][common_len])
		    break;
	    }
2189

2190
	    if (match < num_matches || matches[0][common_len] == '\0')
2191
		break;
2192

2193
2194
	    common_len++;
	}
2195

2196
2197
	mzero = charalloc(lastslash_len + common_len + 1);
	sprintf(mzero, "%.*s%.*s", lastslash_len, buf, common_len,
2198
		matches[0]);
2199

2200
	common_len += lastslash_len;
2201

2202
	assert(common_len >= *place);
2203

2204
2205
2206
2207
2208
	if (num_matches == 1 && is_dir(mzero)) {
	    mzero[common_len] = '/';
	    common_len++;
	    assert(common_len > *place);
	}
2209

2210
2211
2212
	if (num_matches > 1 && (common_len != *place ||
		*lastwastab == FALSE))
	    beep();
2213

2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
	/* If there is more match to display on the statusbar, show it.
	 * We reset lastwastab to FALSE: it requires hitting Tab twice
	 * in succession with no statusbar changes to see a match
	 * list. */
	if (common_len != *place) {
	    size_t buflen = strlen(buf);

	    *lastwastab = FALSE;
	    buf = charealloc(buf, common_len + buflen - *place + 1);
	    charmove(buf + common_len, buf + *place, buflen - *place + 1);
	    strncpy(buf, mzero, common_len);
	    *place = common_len;
	} else if (*lastwastab == FALSE || num_matches < 2)
	    *lastwastab = TRUE;
	else {
	    int longest_name = 0, editline = 0;
	    size_t columns;
2231

2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
	    /* Now we show a list of the available choices. */
	    assert(num_matches > 1);

	    /* Sort the list. */
	    qsort(matches, num_matches, sizeof(char *), diralphasort);

	    for (match = 0; match < num_matches; match++) {
		common_len = strnlenpt(matches[match], COLS - 1);
		if (common_len > COLS - 1) {
		    longest_name = COLS - 1;
2242
2243
		    break;
		}
2244
2245
		if (common_len > longest_name)
		    longest_name = common_len;
2246
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2247

2248
	    assert(longest_name <= COLS - 1);
Chris Allegretta's avatar
Chris Allegretta committed
2249

2250
2251
2252
2253
	    /* Each column will be longest_name + 2 characters wide,
	     * i.e, two spaces between columns, except that there will
	     * be only one space after the last column. */
	    columns = (COLS + 1) / (longest_name + 2);
2254

2255
2256
2257
2258
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2259

2260
2261
	    /* Disable el cursor. */
	    curs_set(0);
2262

2263
2264
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2265

2266
2267
		wmove(edit, editline, (longest_name + 2) *
			(match % columns));
2268

2269
2270
2271
2272
2273
		if (match % columns == 0 && editline == editwinrows - 1
			&& num_matches - match > columns) {
		    waddstr(edit, _("(more)"));
		    break;
		}
2274

2275
2276
2277
2278
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2279

2280
		if ((match + 1) % columns == 0)
2281
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2282
2283
	    }
	    wrefresh(edit);
2284
	    *list = TRUE;
2285
2286
2287
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2288
2289
    }

2290
2291
    free_charptrarray(matches, num_matches);

2292
    /* Only refresh the edit window if we don't have a list of filename
2293
     * matches on it. */
2294
    if (*list == FALSE)
2295
	edit_refresh();
2296
2297

    /* Enable el cursor. */
2298
    curs_set(1);
2299

2300
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2301
}
2302
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2303

2304
2305
/* Only print the last part of a path.  Isn't there a shell command for
 * this? */
2306
2307
const char *tail(const char *foo)
{
2308
    const char *tmp = strrchr(foo, '/');
2309

2310
2311
2312
    if (tmp == NULL)
	tmp = foo;
    else if (*tmp == '/')
2313
2314
2315
2316
2317
	tmp++;

    return tmp;
}

2318
#ifndef DISABLE_BROWSER
2319
/* Free our malloc()ed memory. */
2320
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2321
{
2322
2323
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2324
2325
2326
    free(array);
}

2327
2328
/* Strip one directory from the end of path. */
void striponedir(char *path)
Chris Allegretta's avatar
Chris Allegretta committed
2329
{
2330
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2331

2332
    assert(path != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2333

2334
    tmp = strrchr(path, '/');
2335
2336
    if (tmp != NULL)
 	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2337
2338
}

2339
2340
2341
2342
2343
2344
/* Return a list of files contained in the directory path.  *longest is
 * the maximum display length of a file, up to COLS - 1 (but at least
 * 7).  *numents is the number of files.  We assume path exists and is a
 * directory.  If neither is true, we return NULL. */
char **browser_init(const char *path, int *longest, size_t *numents, DIR
	*dir)
2345
{
2346
    const struct dirent *nextdir;
2347
2348
    char **filelist;
    size_t i, path_len;
2349

2350
    assert(dir != NULL);
2351

2352
    *longest = 0;
2353

2354
    i = 0;
2355

2356
    while ((nextdir = readdir(dir)) != NULL) {
2357
2358
2359
	size_t dlen;

	/* Don't show the . entry. */
2360
	if (strcmp(nextdir->d_name, ".") == 0)
2361
	   continue;
2362
2363
	i++;

2364
	dlen = strlenpt(nextdir->d_name);
2365
2366
	if (dlen > *longest)
	    *longest = dlen;
2367
    }
2368
2369

    *numents = i;
2370
2371
2372
    rewinddir(dir);
    *longest += 10;

2373
    filelist = (char **)nmalloc(*numents * sizeof(char *));
2374

2375
2376
    path_len = strlen(path);

2377
2378
    i = 0;

2379
    while ((nextdir = readdir(dir)) != NULL && i < *numents) {
2380
	/* Don't show the "." entry. */
2381
	if (strcmp(nextdir->d_name, ".") == 0)
2382
2383
	   continue;

2384
2385
	filelist[i] = charalloc(path_len + strlen(nextdir->d_name) + 1);
	sprintf(filelist[i], "%s%s", path, nextdir->d_name);
2386
2387
	i++;
    }
2388
2389
2390
2391
2392

    /* Maybe the number of files in the directory changed between the
     * first time we scanned and the second.  i is the actual length of
     * filelist, so record it. */
    *numents = i;
2393
    closedir(dir);
2394
2395
2396

    if (*longest > COLS - 1)
	*longest = COLS - 1;
2397
2398
    if (*longest < 7)
	*longest = 7;
2399
2400
2401
2402

    return filelist;
}

2403
2404
2405
/* Our browser function.  path is the path to start browsing from.
 * Assume path has already been tilde-expanded. */
char *do_browser(char *path, DIR *dir)
Chris Allegretta's avatar
Chris Allegretta committed
2406
{
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
    int kbinput, longest, selected, width;
    bool meta_key, func_key, old_constupdate = ISSET(CONSTUPDATE);
    size_t numents;
    char **filelist, *retval = NULL;

    curs_set(0);
    blank_statusbar();
    bottombars(browser_list);
    wrefresh(bottomwin);

#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
    /* Set currshortcut so the user can click in the shortcut area, and
     * so the browser help screen will come up. */
    currshortcut = browser_list;
2421
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2422

2423
    UNSET(CONSTUPDATE);
2424

2425
2426
2427
2428
2429
2430
2431
2432
  change_browser_directory:
	/* We go here after the user selects a new directory. */

    kbinput = ERR;
    selected = 0;
    width = 0;

    path = mallocstrassn(path, get_full_path(path));
Chris Allegretta's avatar
Chris Allegretta committed
2433

2434
2435
    /* Assume that path exists and ends with a slash. */
    assert(path != NULL && path[strlen(path) - 1] == '/');
Chris Allegretta's avatar
Chris Allegretta committed
2436

2437
2438
2439
2440
    /* Get the list of files. */
    filelist = browser_init(path, &longest, &numents, dir);

    assert(filelist != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2441

2442
    /* Sort the list. */
Chris Allegretta's avatar
Chris Allegretta committed
2443
2444
2445
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
Chris Allegretta's avatar
Chris Allegretta committed
2446
2447

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2448
    do {
2449
	bool abort = FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2450
	int j, col = 0, editline = 0, fileline;
2451
2452
2453
2454
	int filecols = 0;
	    /* Used only if width == 0, to calculate the number of files
	     * per row below. */
	struct stat st;
2455
2456
	char *new_path;
	    /* Used by the Go To Directory prompt. */
2457
2458
2459
#ifndef DISABLE_MOUSE
	MEVENT mevent;
#endif
Rocco Corsi's avatar
   
Rocco Corsi committed
2460

2461
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2462

2463
2464
	/* Compute the line number we're on now, so that we don't divide
	 * by zero later. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2465
	fileline = selected;
2466
	if (width != 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2467
	    fileline /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2468
2469

	switch (kbinput) {
2470
#ifndef DISABLE_MOUSE
2471
2472
2473
	    case KEY_MOUSE:
		if (getmouse(&mevent) == ERR)
		    break;
2474

2475
2476
2477
2478
2479
2480
2481
2482
2483
		/* If we clicked in the edit window, we probably clicked
		 * on a file. */
		if (wenclose(edit, mevent.y, mevent.x)) {
		    int selectedbackup = selected;

		    mevent.y -= 2;

		    /* longest is the width of each column.  There are
		     * two spaces between each column. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2484
		    selected = (fileline / editwinrows) * editwinrows *
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
			width + 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--;

		    /* If we're off the screen, reset to the last item.
		     * If we clicked the same place as last time, select
		     * this name! */
		    if (selected > numents - 1)
			selected = numents - 1;
		    else if (selectedbackup == selected)
			/* Put back the 'select' key. */
			unget_kbinput('s', FALSE, FALSE);
		} else {
		    /* We must have clicked a shortcut.  Put back the
		     * equivalent shortcut key. */
		    int mouse_x, mouse_y;
		    get_mouseinput(&mouse_x, &mouse_y, TRUE);
		}
2507

2508
		break;
2509
#endif
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
	    case NANO_PREVLINE_KEY:
		if (selected >= width)
		    selected -= width;
		break;
	    case NANO_BACK_KEY:
		if (selected > 0)
		    selected--;
		break;
	    case NANO_NEXTLINE_KEY:
		if (selected + width <= numents - 1)
		    selected += width;
		break;
	    case NANO_FORWARD_KEY:
		if (selected < numents - 1)
		    selected++;
		break;
	    case NANO_PREVPAGE_KEY:
	    case NANO_PREVPAGE_FKEY:
	    case '-': /* Pico compatibility. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2529
		if (selected >= (editwinrows + fileline % editwinrows) *
2530
			width)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2531
		    selected -= (editwinrows + fileline % editwinrows) *
2532
2533
2534
2535
2536
2537
2538
			width;
		else
		    selected = 0;
		break;
	    case NANO_NEXTPAGE_KEY:
	    case NANO_NEXTPAGE_FKEY:
	    case ' ': /* Pico compatibility. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2539
		selected += (editwinrows - fileline % editwinrows) *
2540
2541
2542
2543
2544
2545
2546
			width;
		if (selected >= numents)
		    selected = numents - 1;
		break;
	    case NANO_HELP_KEY:
	    case NANO_HELP_FKEY:
	    case '?': /* Pico compatibility. */
2547
#ifndef DISABLE_HELP
2548
2549
		do_help();
		curs_set(0);
2550
#else
2551
		nano_disabled_msg();
2552
#endif
Rocco Corsi's avatar
   
Rocco Corsi committed
2553
		break;
2554
2555
2556
2557
2558
2559
2560
2561
2562
	    case NANO_ENTER_KEY:
	    case 'S': /* Pico compatibility. */
	    case 's':
		/* You can't move up from "/". */
		if (strcmp(filelist[selected], "/..") == 0) {
		    statusbar(_("Can't move up a directory"));
		    beep();
		    break;
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2563

2564
#ifndef DISABLE_OPERATINGDIR
2565
2566
2567
2568
2569
2570
		/* Note: the selected file can be outside the operating
		 * directory if it's ".." or if it's a symlink to a
		 * directory outside the operating directory. */
		if (check_operating_dir(filelist[selected], FALSE)) {
		    statusbar(
			_("Can't go outside of %s in restricted mode"),
2571
			operating_dir);
2572
2573
2574
		    beep();
		    break;
		}
2575
2576
#endif

2577
2578
2579
2580
2581
2582
		if (stat(filelist[selected], &st) == -1) {
		    statusbar(_("Error reading %s: %s"),
			filelist[selected], strerror(errno));
		    beep();
		    break;
		}
2583

2584
2585
2586
2587
2588
		if (!S_ISDIR(st.st_mode)) {
		    retval = mallocstrcpy(retval, filelist[selected]);
		    abort = TRUE;
		    break;
		}
2589

2590
2591
2592
2593
2594
2595
2596
		dir = opendir(filelist[selected]);
		if (dir == NULL) {
		    /* We can't open this dir for some reason.
		     * Complain. */
		    statusbar(_("Error reading %s: %s"),
			filelist[selected], strerror(errno));
		    break;
Chris Allegretta's avatar
Chris Allegretta committed
2597
		}
Chris Allegretta's avatar
Chris Allegretta committed
2598

2599
		path = mallocstrcpy(path, filelist[selected]);
2600

2601
2602
2603
2604
		/* Start over again with the new path value. */
		free_charptrarray(filelist, numents);
		goto change_browser_directory;

2605
2606
2607
2608
2609
	    /* Refresh the screen. */
	    case NANO_REFRESH_KEY:
		total_update();
		break;

2610
2611
2612
2613
2614
2615
2616
2617
	    /* Go to a specific directory. */
	    case NANO_GOTOLINE_KEY:
	    case NANO_GOTOLINE_FKEY:
	    case 'G': /* Pico compatibility. */
	    case 'g':
		curs_set(1);

		j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2618
#ifndef NANO_SMALL
2619
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2620
#endif
2621
			_("Go To Directory"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2622

2623
2624
		curs_set(0);
		bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2625

2626
2627
2628
2629
		if (j < 0) {
		    statusbar(_("Cancelled"));
		    break;
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2630

2631
2632
2633
2634
2635
2636
2637
		new_path = real_dir_from_tilde(answer);

		if (new_path[0] != '/') {
		    new_path = charealloc(new_path, strlen(path) +
			strlen(answer) + 1);
		    sprintf(new_path, "%s%s", path, answer);
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2638

2639
#ifndef DISABLE_OPERATINGDIR
2640
2641
2642
2643
2644
2645
2646
		if (check_operating_dir(new_path, FALSE)) {
		    statusbar(
			_("Can't go outside of %s in restricted mode"),
			operating_dir);
		    free(new_path);
		    break;
		}
2647
2648
#endif

2649
2650
2651
2652
2653
2654
2655
2656
2657
		dir = opendir(new_path);
		if (dir == NULL) {
		    /* We can't open this dir for some reason.
		     * Complain. */
		    statusbar(_("Error reading %s: %s"), answer,
			strerror(errno));
		    free(new_path);
		    break;
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2658

2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
		/* Start over again with the new path value. */
		free(path);
		path = new_path;
		free_charptrarray(filelist, numents);
		goto change_browser_directory;

	    /* Abort the browser. */
	    case NANO_EXIT_KEY:
	    case NANO_EXIT_FKEY:
	    case 'E': /* Pico compatibility. */
	    case 'e':
		abort = TRUE;
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2672
	}
2673

Chris Allegretta's avatar
Chris Allegretta committed
2674
2675
2676
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2677
2678
	blank_edit();

2679
	if (width != 0)
2680
2681
	    j = width * editwinrows *
		((selected / width) / editwinrows);
Chris Allegretta's avatar
Chris Allegretta committed
2682
	else
2683
	    j = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2684
2685
2686

	wmove(edit, 0, 0);

2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
	{
	    int foo_len = mb_cur_max() * 7;
	    char *foo = charalloc(foo_len + 1);

	    for (; j < numents && editline <= editwinrows - 1; j++) {
		char *disp = display_string(tail(filelist[j]), 0,
			longest, FALSE);

		/* Highlight the currently selected file/dir. */
		if (j == selected)
		    wattron(edit, A_REVERSE);

		mvwaddnstr(edit, editline, col, hblank, longest);
		mvwaddstr(edit, editline, col, disp);
		free(disp);

		col += longest;
		filecols++;

		/* Show file info also.  We don't want to report file
		 * sizes for links, so we use lstat().  Also, stat() and
		 * lstat() return an error if, for example, the file is
		 * deleted while the file browser is open.  In that
		 * case, we report "--" as the file info. */
		if (lstat(filelist[j], &st) == -1 ||
			S_ISLNK(st.st_mode)) {
		    /* Aha!  It's a symlink!  Now, is it a dir?  If so,
		     * mark it as such. */
		    if (stat(filelist[j], &st) == 0 &&
			S_ISDIR(st.st_mode)) {
			strncpy(foo, _("(dir)"), foo_len);
			foo[foo_len] = '\0';
		    } else
			strcpy(foo, "--");
		} else if (S_ISDIR(st.st_mode))
		    strncpy(foo, _("(dir)"), foo_len);
		else if (st.st_size < (1 << 10)) /* less than 1 k. */
		    sprintf(foo, "%4u  B", (unsigned int)st.st_size);
		else if (st.st_size < (1 << 20)) /* less than 1 meg. */
		    sprintf(foo, "%4u KB",
			(unsigned int)(st.st_size >> 10));
		else if (st.st_size < (1 << 30)) /* less than 1 gig. */
		    sprintf(foo, "%4u MB",
			(unsigned int)(st.st_size >> 20));
		else
		    sprintf(foo, "%4u GB",
			(unsigned int)(st.st_size >> 30));

		mvwaddnstr(edit, editline, col - strlen(foo), foo,
			foo_len);

		if (j == selected)
		    wattroff(edit, A_REVERSE);

		/* Add some space between the columns. */
		col += 2;

		/* If the next entry isn't going to fit on the line,
		 * move to the next line. */
		if (col > COLS - longest) {
		    editline++;
		    col = 0;
		    if (width == 0)
			width = filecols;
		}
		wmove(edit, editline, col);
Chris Allegretta's avatar
Chris Allegretta committed
2753
	    }
2754
2755

	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2756
	}
2757

2758
	wrefresh(edit);
2759
2760
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
2761

Chris Allegretta's avatar
Chris Allegretta committed
2762
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2763
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2764
    edit_refresh();
2765
2766
2767
    curs_set(1);
    if (old_constupdate)
	SET(CONSTUPDATE);
Chris Allegretta's avatar
Chris Allegretta committed
2768

2769
    /* Clean up. */
Chris Allegretta's avatar
Chris Allegretta committed
2770
    free_charptrarray(filelist, numents);
2771
2772
    free(path);

Chris Allegretta's avatar
Chris Allegretta committed
2773
2774
    return retval;
}
2775

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2776
2777
2778
/* The file browser front end.  We check to see if inpath has a dir in
 * it.  If it does, we start do_browser() from there.  Otherwise, we
 * start do_browser() from the current directory. */
2779
char *do_browse_from(const char *inpath)
2780
2781
{
    struct stat st;
2782
    char *path;
2783
	/* This holds the tilde-expanded version of inpath. */
2784
    DIR *dir = NULL;
2785

2786
2787
2788
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2789

2790
2791
2792
2793
2794
2795
    /* Perhaps path is a directory.  If so, we'll pass it to
     * do_browser().  Or perhaps path is a directory / a file.  If so,
     * we'll try stripping off the last path element and passing it to
     * do_browser().  Or perhaps path doesn't have a directory portion
     * at all.  If so, we'll just pass the current directory to
     * do_browser(). */
2796
2797
    if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	striponedir(path);
2798
2799
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2800

2801
2802
	    path = charalloc(PATH_MAX + 1);
	    path = getcwd(path, PATH_MAX + 1);
2803
2804
2805

	    if (path != NULL)
		align(&path);
2806
	}
2807
    }
2808

2809
#ifndef DISABLE_OPERATINGDIR
2810
2811
2812
2813
2814
2815
2816
    /* If the resulting path isn't in the operating directory, use
     * the operating directory instead. */
    if (check_operating_dir(path, FALSE)) {
	if (path != NULL)
	    free(path);
	path = mallocstrcpy(NULL, operating_dir);
    }
2817
2818
#endif

2819
2820
2821
    if (path != NULL)
	dir = opendir(path);

2822
    if (dir == NULL) {
2823
	beep();
2824
2825
2826
2827
2828
	free(path);
	return NULL;
    }

    return do_browser(path, dir);
2829
}
Chris Allegretta's avatar
Chris Allegretta committed
2830
#endif /* !DISABLE_BROWSER */
2831

2832
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2833
2834
2835
/* 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)
2836
{
2837
    char *nanohist = NULL;
2838

2839
2840
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2841

2842
2843
2844
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2845
    }
2846
2847
2848
2849
2850
2851
    return nanohist;
}

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2853
    /* Assume do_rcfile() has reported a missing home directory. */
2854
2855
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2856

2857
	if (hist == NULL) {
2858
	    if (errno != ENOENT) {
2859
2860
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2861
2862
2863
2864
		rcfile_error(N_("Error reading %s: %s"), nanohist,
			strerror(errno));
		fprintf(stderr,
			_("\nPress Return to continue starting nano\n"));
2865
2866
		while (getchar() != '\n')
		    ;
2867
	    }
2868
	} else {
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
	    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
2883
2884
2885
		    history = &replace_history;
	    }
	    fclose(hist);
2886
	    free(line);
2887
2888
	    UNSET(HISTORY_CHANGED);
	}
2889
2890
2891
2892
2893
2894
	free(nanohist);
    }
}

bool writehist(FILE *hist, historyheadtype *histhead)
{
2895
    historytype *p;
2896

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2897
    /* Write oldest history first. */
2898
2899
    for (p = histhead->tail; p->prev != NULL; p = p->prev) {
	size_t p_len = strlen(p->data);
2900

2901
2902
	sunder(p->data);
	if (fwrite(p->data, sizeof(char), p_len, hist) < p_len ||
2903
2904
		putc('\n', hist) == EOF)
	    return FALSE;
2905
    }
2906
    return TRUE;
2907
2908
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2909
/* Save histories to ~/.nano_history. */
2910
2911
void save_history(void)
{
2912
    char *nanohist;
2913

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2914
    /* Don't save unchanged or empty histories. */
2915
    if ((search_history.count == 0 && replace_history.count == 0) ||
2916
	!ISSET(HISTORY_CHANGED) || ISSET(VIEW_MODE))
2917
2918
	return;

2919
2920
2921
2922
    nanohist = histfilename();

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

2924
	if (hist == NULL)
2925
2926
	    rcfile_error(N_("Error writing %s: %s"), nanohist,
		strerror(errno));
2927
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2928
2929
	    /* Make sure no one else can read from or write to the
	     * history file. */
2930
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2931
2932
2933
2934

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
2935
2936
		rcfile_error(N_("Error writing %s: %s"), nanohist,
			strerror(errno));
2937
2938
2939
2940
2941
	    fclose(hist);
	}
	free(nanohist);
    }
}
2942
#endif /* !NANO_SMALL && ENABLE_NANORC */