files.c 76.6 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
/* 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
64
65
66
67
 * file.  Otherwise, we assume prevnode is the last line of the file,
 * and put our line after prevnode. */
filestruct *read_line(char *buf, filestruct *prevnode, 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
    } else {
102
	assert(prevnode != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
103

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

    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
    newnode->filename = NULL;
716

Chris Allegretta's avatar
Chris Allegretta committed
717
718
719
720
721
    return newnode;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

911
    assert(open_files != NULL);
912

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

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

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

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

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

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

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

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

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

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

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

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

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

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

980
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
981
/* When passed "[relative path]" or "[relative path][filename]" in
982
 * origpath, return "[full path]" or "[full path][filename]" on success,
983
984
985
986
 * 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. */
987
char *get_full_path(const char *origpath)
988
{
989
    char *d_here, *d_there = NULL;
990

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

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

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

1004
1005
	align(&d_here);

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

1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
	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. */
1025
	if (path_only) {
1026
1027
1028
1029
	    size_t d_there_len = strlen(d_there);

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

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

1037
1038
1039
1040
	/* 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);
1041

1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
	    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) {
1057
		free(d_there);
1058
1059
1060
1061
		d_there = NULL;
	    } else {
		/* Get the full path and save it in d_there. */
		free(d_there);
1062

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

1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
		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
1076
1077
1078
1079
1080
1081
1082
1083
		    /* 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);
1084
1085
	    }

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

1090
1091
1092
1093
1094
	/* 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. */
1095
	if (!path_only && d_there != NULL) {
1096
1097
1098
1099
1100
1101
	    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. */
1102
1103
1104
	free(d_there_file);
    }

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

#ifndef DISABLE_SPELLER
1110
1111
1112
/* 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. */
1113
char *check_writable_directory(const char *path)
1114
{
1115
1116
    char *full_path = get_full_path(path);

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

1121
1122
1123
1124
    /* 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] != '/') {
1125
	free(full_path);
1126
	return NULL;
1127
    }
1128
1129
1130
1131
1132

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

1133
1134
1135
1136
/* 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)
1137
{
1138
1139
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1140
    int filedesc;
1141

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

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

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

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

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

    free(full_tempdir);
1171

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

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

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

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

1197
1198
1199
1200
1201
/* 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)
1202
{
1203
1204
1205
1206
    /* 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. */
1207

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

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

1216
    assert(full_operating_dir != NULL);
1217
1218

    fullpath = get_full_path(currpath);
1219
1220

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

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

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

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

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

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

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

1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
    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
1294

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

1300
1301
1302
    return retval;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1407
	if (f == NULL) {
1408
	    statusbar(_("Error reading %s: %s"), realname,
1409
		strerror(errno));
1410
	    goto cleanup_and_exit;
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
1444
	/* 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);
	}
1445

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

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

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

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1487
1488
    /* 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
1489
1490
1491
1492
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1493
	goto cleanup_and_exit;
1494
    }
1495

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    retval = 1;

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

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

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

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

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

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

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

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

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

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

1741
    currshortcut = writefile_list;
1742

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    free(ans);
1907

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

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

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

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

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

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

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

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

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

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

1960
    return dirtmp;
1961
1962
}

1963
#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
1964
1965
/* Our sort routine for file listings.  Sort alphabetically and
 * case-insensitively, and sort directories before filenames. */
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
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
1983
#ifndef DISABLE_TABCOMP
1984
1985
/* Is the given file a directory? */
int is_dir(const char *buf)
1986
{
1987
    char *dirptr = real_dir_from_tilde(buf);
1988
1989
    struct stat fileinfo;

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

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

1995
    free(dirptr);
1996

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

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

2017
2018
2019
2020
/* 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
2021
{
2022
2023
    char **matches = NULL;
    const struct passwd *userdata;
2024

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

2027
    *num_matches = 0;
2028

2029
    while ((userdata = getpwent()) != NULL) {
2030
2031
2032
	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... */
2033

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

2041
2042
2043
2044
2045
	    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);
2046
	    ++(*num_matches);
2047
	}
2048
2049
    }
    endpwent();
2050

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

2054
/* This was originally called exe_n_cwd_tab_completion(), but we're not
2055
2056
2057
 * 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
2058
{
2059
2060
2061
2062
2063
2064
2065
    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
2066
    DIR *dir;
2067
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2068

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

2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
    *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
2085
    } else {
2086
2087
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2088
2089
    }

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

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

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

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

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

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

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

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

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

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

2150
2151
2152
/* 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
2153
{
2154
2155
    size_t num_matches = 0;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2156

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

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

2161
2162
2163
2164
    /* 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
2165

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

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

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

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

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

2194
2195
	    common_len++;
	}
2196

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

2201
	common_len += lastslash_len;
2202

2203
	assert(common_len >= *place);
2204

2205
2206
2207
	if (num_matches == 1 && is_dir(mzero)) {
	    mzero[common_len] = '/';
	    common_len++;
2208

2209
2210
	    assert(common_len > *place);
	}
2211

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

2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
	/* 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);
2226
	    charcpy(buf, mzero, common_len);
2227
2228
2229
2230
2231
2232
	    *place = common_len;
	} else if (*lastwastab == FALSE || num_matches < 2)
	    *lastwastab = TRUE;
	else {
	    int longest_name = 0, editline = 0;
	    size_t columns;
2233

2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
	    /* 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;
2244
2245
		    break;
		}
2246
2247
		if (common_len > longest_name)
		    longest_name = common_len;
2248
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2249

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

2252
2253
2254
2255
	    /* 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);
2256

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

2262
2263
	    /* Disable el cursor. */
	    curs_set(0);
2264

2265
2266
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2267

2268
2269
		wmove(edit, editline, (longest_name + 2) *
			(match % columns));
2270

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

2278
2279
2280
2281
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2282

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

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2291
2292
    }

2293
2294
    free_charptrarray(matches, num_matches);

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

    /* Enable el cursor. */
2301
    curs_set(1);
2302

2303
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2304
}
2305
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2306

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

2313
2314
2315
    if (tmp == NULL)
	tmp = foo;
    else if (*tmp == '/')
2316
2317
2318
2319
2320
	tmp++;

    return tmp;
}

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

Chris Allegretta's avatar
Chris Allegretta committed
2328
2329
2330
    free(array);
}

2331
2332
/* Strip one directory from the end of path. */
void striponedir(char *path)
Chris Allegretta's avatar
Chris Allegretta committed
2333
{
2334
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2335

2336
    assert(path != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2337

2338
    tmp = strrchr(path, '/');
2339

2340
2341
    if (tmp != NULL)
 	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2342
2343
}

2344
2345
2346
2347
2348
2349
/* 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)
2350
{
2351
    const struct dirent *nextdir;
2352
2353
    char **filelist;
    size_t i, path_len;
2354

2355
    assert(dir != NULL);
2356

2357
    *longest = 0;
2358

2359
    i = 0;
2360

2361
    while ((nextdir = readdir(dir)) != NULL) {
2362
2363
2364
	size_t dlen;

	/* Don't show the . entry. */
2365
	if (strcmp(nextdir->d_name, ".") == 0)
2366
	   continue;
2367
2368
	i++;

2369
	dlen = strlenpt(nextdir->d_name);
2370
2371
	if (dlen > *longest)
	    *longest = dlen;
2372
    }
2373
2374

    *numents = i;
2375
2376
2377
    rewinddir(dir);
    *longest += 10;

2378
    filelist = (char **)nmalloc(*numents * sizeof(char *));
2379

2380
2381
    path_len = strlen(path);

2382
2383
    i = 0;

2384
    while ((nextdir = readdir(dir)) != NULL && i < *numents) {
2385
	/* Don't show the "." entry. */
2386
	if (strcmp(nextdir->d_name, ".") == 0)
2387
2388
	   continue;

2389
2390
	filelist[i] = charalloc(path_len + strlen(nextdir->d_name) + 1);
	sprintf(filelist[i], "%s%s", path, nextdir->d_name);
2391
2392
	i++;
    }
2393
2394
2395
2396
2397

    /* 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;
2398
    closedir(dir);
2399
2400
2401

    if (*longest > COLS - 1)
	*longest = COLS - 1;
2402
2403
    if (*longest < 7)
	*longest = 7;
2404
2405
2406
2407

    return filelist;
}

2408
2409
2410
/* 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
2411
{
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
    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;
2426
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2427

2428
    UNSET(CONSTUPDATE);
2429

2430
2431
2432
2433
2434
2435
2436
2437
  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
2438

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

2442
2443
2444
2445
    /* Get the list of files. */
    filelist = browser_init(path, &longest, &numents, dir);

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

2447
    /* Sort the list. */
Chris Allegretta's avatar
Chris Allegretta committed
2448
2449
2450
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
Chris Allegretta's avatar
Chris Allegretta committed
2451
2452

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

2466
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2467

2468
2469
	/* 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
2470
	fileline = selected;
2471
	if (width != 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2472
	    fileline /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2473
2474

	switch (kbinput) {
2475
#ifndef DISABLE_MOUSE
2476
2477
2478
	    case KEY_MOUSE:
		if (getmouse(&mevent) == ERR)
		    break;
2479

2480
2481
2482
2483
2484
2485
2486
2487
2488
		/* 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
2489
		    selected = (fileline / editwinrows) * editwinrows *
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
			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);
		}
2512

2513
		break;
2514
#endif
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
	    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
2534
		if (selected >= (editwinrows + fileline % editwinrows) *
2535
			width)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2536
		    selected -= (editwinrows + fileline % editwinrows) *
2537
2538
2539
2540
2541
2542
2543
			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
2544
		selected += (editwinrows - fileline % editwinrows) *
2545
2546
2547
2548
2549
2550
2551
			width;
		if (selected >= numents)
		    selected = numents - 1;
		break;
	    case NANO_HELP_KEY:
	    case NANO_HELP_FKEY:
	    case '?': /* Pico compatibility. */
2552
#ifndef DISABLE_HELP
2553
2554
		do_help();
		curs_set(0);
2555
#else
2556
		nano_disabled_msg();
2557
#endif
Rocco Corsi's avatar
   
Rocco Corsi committed
2558
		break;
2559
2560
2561
2562
2563
2564
2565
2566
2567
	    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
2568

2569
#ifndef DISABLE_OPERATINGDIR
2570
2571
2572
2573
2574
2575
		/* 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"),
2576
			operating_dir);
2577
2578
2579
		    beep();
		    break;
		}
2580
2581
#endif

2582
2583
2584
2585
2586
2587
		if (stat(filelist[selected], &st) == -1) {
		    statusbar(_("Error reading %s: %s"),
			filelist[selected], strerror(errno));
		    beep();
		    break;
		}
2588

2589
2590
2591
2592
2593
		if (!S_ISDIR(st.st_mode)) {
		    retval = mallocstrcpy(retval, filelist[selected]);
		    abort = TRUE;
		    break;
		}
2594

2595
2596
2597
2598
2599
2600
2601
		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
2602
		}
Chris Allegretta's avatar
Chris Allegretta committed
2603

2604
		path = mallocstrcpy(path, filelist[selected]);
2605

2606
2607
2608
2609
		/* Start over again with the new path value. */
		free_charptrarray(filelist, numents);
		goto change_browser_directory;

2610
2611
2612
2613
2614
	    /* Refresh the screen. */
	    case NANO_REFRESH_KEY:
		total_update();
		break;

2615
2616
2617
2618
2619
2620
2621
2622
	    /* 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
2623
#ifndef NANO_SMALL
2624
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2625
#endif
2626
			_("Go To Directory"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2627

2628
2629
		curs_set(0);
		bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2630

2631
2632
2633
2634
		if (j < 0) {
		    statusbar(_("Cancelled"));
		    break;
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2635

2636
2637
2638
2639
2640
2641
2642
		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
2643

2644
#ifndef DISABLE_OPERATINGDIR
2645
2646
2647
2648
2649
2650
2651
		if (check_operating_dir(new_path, FALSE)) {
		    statusbar(
			_("Can't go outside of %s in restricted mode"),
			operating_dir);
		    free(new_path);
		    break;
		}
2652
2653
#endif

2654
2655
2656
2657
2658
2659
2660
2661
2662
		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
2663

2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
		/* 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
2677
	}
2678

Chris Allegretta's avatar
Chris Allegretta committed
2679
2680
2681
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2682
2683
	blank_edit();

2684
	if (width != 0)
2685
2686
	    j = width * editwinrows *
		((selected / width) / editwinrows);
Chris Allegretta's avatar
Chris Allegretta committed
2687
	else
2688
	    j = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2689
2690
2691

	wmove(edit, 0, 0);

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
	{
	    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)) {
2722
			charcpy(foo, _("(dir)"), foo_len);
2723
2724
2725
			foo[foo_len] = '\0';
		    } else
			strcpy(foo, "--");
2726
		} else if (S_ISDIR(st.st_mode)) {
2727
		    charcpy(foo, _("(dir)"), foo_len);
2728
2729
		    foo[foo_len] = '\0';
		} else if (st.st_size < (1 << 10)) /* less than 1 k. */
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
		    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
2759
	    }
2760
2761

	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2762
	}
2763

2764
	wrefresh(edit);
2765
2766
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
2767

Chris Allegretta's avatar
Chris Allegretta committed
2768
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2769
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2770
    edit_refresh();
2771
2772
2773
    curs_set(1);
    if (old_constupdate)
	SET(CONSTUPDATE);
Chris Allegretta's avatar
Chris Allegretta committed
2774

2775
    /* Clean up. */
Chris Allegretta's avatar
Chris Allegretta committed
2776
    free_charptrarray(filelist, numents);
2777
2778
    free(path);

Chris Allegretta's avatar
Chris Allegretta committed
2779
2780
    return retval;
}
2781

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2782
2783
2784
/* 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. */
2785
char *do_browse_from(const char *inpath)
2786
2787
{
    struct stat st;
2788
    char *path;
2789
	/* This holds the tilde-expanded version of inpath. */
2790
    DIR *dir = NULL;
2791

2792
2793
2794
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2795

2796
2797
2798
2799
2800
2801
    /* 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(). */
2802
2803
    if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	striponedir(path);
2804
2805
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2806

2807
2808
	    path = charalloc(PATH_MAX + 1);
	    path = getcwd(path, PATH_MAX + 1);
2809
2810
2811

	    if (path != NULL)
		align(&path);
2812
	}
2813
    }
2814

2815
#ifndef DISABLE_OPERATINGDIR
2816
2817
2818
2819
2820
2821
2822
    /* 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);
    }
2823
2824
#endif

2825
2826
2827
    if (path != NULL)
	dir = opendir(path);

2828
    if (dir == NULL) {
2829
	beep();
2830
2831
2832
2833
2834
	free(path);
	return NULL;
    }

    return do_browser(path, dir);
2835
}
Chris Allegretta's avatar
Chris Allegretta committed
2836
#endif /* !DISABLE_BROWSER */
2837

2838
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2839
2840
2841
/* 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)
2842
{
2843
    char *nanohist = NULL;
2844

2845
2846
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2847

2848
2849
2850
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2851
    }
2852
2853
2854
2855
2856
2857
    return nanohist;
}

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

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

2863
	if (hist == NULL) {
2864
	    if (errno != ENOENT) {
2865
2866
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2867
2868
2869
2870
		rcfile_error(N_("Error reading %s: %s"), nanohist,
			strerror(errno));
		fprintf(stderr,
			_("\nPress Return to continue starting nano\n"));
2871
2872
		while (getchar() != '\n')
		    ;
2873
	    }
2874
	} else {
2875
2876
2877
2878
2879
2880
	    /* Load a history (first the search history, then the
	     * replace history) from oldest to newest.  Assume the last
	     * history entry is a blank line. */
	    filestruct **history = &search_history;
	    filestruct **historyage = &searchage;
	    filestruct **historybot = &searchbot;
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
	    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);
2892
2893
2894
		    update_history(history, historyage, historybot,
			line);
		} else {
2895
		    history = &replace_history;
2896
2897
2898
		    historyage = &replaceage;
		    historybot = &replacebot;
		}
2899
	    }
2900

2901
	    fclose(hist);
2902
	    free(line);
2903
	}
2904
2905
2906
2907
	free(nanohist);
    }
}

2908
bool writehist(FILE *hist, filestruct *h)
2909
{
2910
    filestruct *p;
2911

2912
2913
2914
    /* Write history from oldest to newest.  Assume the last history
     * entry is a blank line. */
    for (p = h; p != NULL; p = p->next) {
2915
	size_t p_len = strlen(p->data);
2916

2917
	sunder(p->data);
2918

2919
	if (fwrite(p->data, sizeof(char), p_len, hist) < p_len ||
2920
2921
		putc('\n', hist) == EOF)
	    return FALSE;
2922
    }
2923

2924
    return TRUE;
2925
2926
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2927
/* Save histories to ~/.nano_history. */
2928
2929
void save_history(void)
{
2930
    char *nanohist;
2931

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2932
    /* Don't save unchanged or empty histories. */
2933
2934
    if (!history_has_changed() || (searchbot->lineno == 1 &&
	replacebot->lineno == 1))
2935
2936
	return;

2937
2938
2939
2940
    nanohist = histfilename();

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

2942
	if (hist == NULL)
2943
2944
	    rcfile_error(N_("Error writing %s: %s"), nanohist,
		strerror(errno));
2945
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2946
2947
	    /* Make sure no one else can read from or write to the
	     * history file. */
2948
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2949

2950
2951
	    if (!writehist(hist, searchage) || !writehist(hist,
		replaceage))
2952
2953
		rcfile_error(N_("Error writing %s: %s"), nanohist,
			strerror(errno));
2954

2955
2956
	    fclose(hist);
	}
2957

2958
2959
2960
	free(nanohist);
    }
}
2961
#endif /* !NANO_SMALL && ENABLE_NANORC */