files.c 74.9 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
11
12
13
14
15
16
17
18
19
20
21
 *   any later version.                                                   *
 *                                                                        *
 *   This program is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of       *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
 *   GNU General Public License for more details.                         *
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            *
 *                                                                        *
 **************************************************************************/

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

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

void read_file(FILE *f, const char *filename)
Chris Allegretta's avatar
Chris Allegretta committed
129
{
130
131
    size_t num_lines = 0;
	/* The number of lines in the file. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
132
133
    size_t num_chars;
	/* The number of characters in the file. */
134
135
136
137
138
139
140
141
    size_t len = 0;
	/* The length of the current line of the file. */
    size_t i = 0;
	/* The position in the current line of the file. */
    size_t bufx = 128;
	/* The size of each chunk of the file that we read. */
    char input = '\0';
	/* The current input character. */
Chris Allegretta's avatar
Chris Allegretta committed
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') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
183

184
#ifndef NANO_SMALL
185
186
	    /* 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
187
188
	     * currently think it's a Mac file. */
	    if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r' &&
189
190
		(format == 0 || format == 2))
		format++;
191
192
193
194
#endif

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

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

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

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

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
232
	    /* Now we allocate a bigger buffer 128 characters at a time.
233
234
235
	     * 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
237
	    if (i >= bufx - 1) {
		bufx += 128;
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
248
    /* This conditional duplicates previous read_byte() behavior.
     * Perhaps this could use some better handling. */
249
250
251
    if (ferror(f))
	nperror(filename);
    fclose(f);
Chris Allegretta's avatar
Chris Allegretta committed
252

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

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

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

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

281
282
283
284
    free(buf);

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

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

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

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

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

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

342
    assert(f != NULL);
343

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

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

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

386
    buf = charalloc(namelen + num_of_digits(INT_MAX) + 7);
387
    strcpy(buf, name);
388
389
    strcpy(buf + namelen, ".save");
    namelen += 5;
390

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

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

	i++;
400
	sprintf(buf + namelen, ".%d", i);
401
402
    }

Chris Allegretta's avatar
Chris Allegretta committed
403
    /* We get here only if there is no possible save file. */
404
    null_at(&buf, 0);
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
564
	    statusbar(_("Cancelled"));
	    break;
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
565
	    size_t pww_save = placewewant;
566

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

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

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

582
583
		if (tmp == NULL)
		    continue;
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

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

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

#ifdef ENABLE_MULTIBUFFER
627
	    if (!ISSET(MULTIBUFFER))
628
#endif
629
630
	    {
		filestruct *top_save = fileage;
631

632
633
634
635
636
637
638
639
640
		/* 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
641
		/* If we didn't insert into a new buffer, unpartition
642
643
644
645
		 * 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. */
646
		unpartition_filestruct(&filepart);
647

648
649
650
		/* Renumber starting with the beginning line of the old
		 * partition. */
		renumber(top_save);
651

652
653
		/* Set edittop back to what it was before. */
		edittop = edittop_save;
654
	    }
655

656
#ifdef ENABLE_MULTIBUFFER
657
658
659
	    if (ISSET(MULTIBUFFER)) {
		/* Update the titlebar. */
		titlebar(NULL);
660

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

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

674
675
676
677
678
679
	    /* Refresh the screen. */
	    edit_refresh();

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
681
    free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
682
683
}

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

    display_main_list();
}

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

/* Splice a node into an existing openfilestruct. */
void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
712
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
713
{
714
    assert(newnode != NULL && begin != NULL);
715

Chris Allegretta's avatar
Chris Allegretta committed
716
717
718
719
720
721
722
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

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

728
729
730
    fileptr->prev->next = fileptr->next;
    fileptr->next->prev = fileptr->prev;
    delete_opennode(fileptr);
Chris Allegretta's avatar
Chris Allegretta committed
731
732
733
734
735
}

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

738
739
740
    free(fileptr->filename);
    free_filestruct(fileptr->fileage);
    free(fileptr);
Chris Allegretta's avatar
Chris Allegretta committed
741
742
}

743
744
745
#ifdef DEBUG
/* Deallocate all memory associated with this and later files, including
 * the lines of text. */
Chris Allegretta's avatar
Chris Allegretta committed
746
747
void free_openfilestruct(openfilestruct *src)
{
748
    assert(src != NULL);
749

750
751
752
    while (src != src->next) {
	src = src->next;
	delete_opennode(src->prev);
Chris Allegretta's avatar
Chris Allegretta committed
753
    }
754
    delete_opennode(src);
Chris Allegretta's avatar
Chris Allegretta committed
755
}
756
#endif
Chris Allegretta's avatar
Chris Allegretta committed
757

758
/* Add/update an entry to the open_files openfilestruct.  If update is
759
 * FALSE, a new entry is created; otherwise, the current entry is
760
 * updated. */
761
void add_open_file(bool update)
762
{
763
    if (open_files == NULL && update)
764
	return;
765

766
767
768
769
770
771
772
773
774
    /* 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);
775
776
777
	open_files = open_files->next;
    }

778
    /* Save the current filename. */
779
    open_files->filename = mallocstrcpy(open_files->filename, filename);
780

781
#ifndef NANO_SMALL
782
    /* Save the current file's stat. */
783
784
785
    open_files->originalfilestat = originalfilestat;
#endif

786
787
788
789
790
791
792
793
794
    /* 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;
795

796
797
    /* Save the current cursor position. */
    open_files->current_x = current_x;
798

799
800
    /* Save the current place we want. */
    open_files->placewewant = placewewant;
801

802
803
    /* Save the current total number of lines. */
    open_files->totlines = totlines;
804

805
806
    /* Save the current total size. */
    open_files->totsize = totsize;
807

808
809
    /* Start with no flags saved. */
    open_files->flags = 0;
810

811
812
813
    /* Save the current modification status. */
    if (ISSET(MODIFIED))
	open_files->flags |= MODIFIED;
814

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
815
#ifndef NANO_SMALL
816
817
818
819
820
    /* 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
821
822
    }

823
824
825
    /* Save the current file format. */
    open_files->fmt = fmt;
#endif
826
827

#ifdef DEBUG
828
    fprintf(stderr, "filename is %s\n", open_files->filename);
829
830
831
#endif
}

832
/* Read the current entry in the open_files structure and set up the
833
 * currently open file buffer using that entry's information. */
834
void load_open_file(void)
835
{
836
    assert(open_files != NULL);
837

838
    /* Restore the current filename. */
839
    filename = mallocstrcpy(filename, open_files->filename);
840

841
#ifndef NANO_SMALL
842
    /* Restore the current file's stat. */
843
844
    originalfilestat = open_files->originalfilestat;
#endif
845
846

    /* Restore the current file buffer. */
847
    fileage = open_files->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
848
    filebot = open_files->filebot;
849

850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
    /* 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
870
871
872
873
874
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

#ifndef NANO_SMALL
875
876
877
878
    /* 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
879
880
881
	SET(MARK_ISSET);
    } else
	UNSET(MARK_ISSET);
882

883
884
    /* Restore the current file format. */
    fmt = open_files->fmt;
Chris Allegretta's avatar
Chris Allegretta committed
885
#endif
886

Chris Allegretta's avatar
Chris Allegretta committed
887
888
889
#ifdef ENABLE_COLOR
    update_color();
#endif
890
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
891

892
    /* Update the titlebar. */
893
894
895
896
    clearok(topwin, FALSE);
    titlebar(NULL);
}

897
/* Open either the next or previous file buffer. */
898
void open_prevnext_file(bool next)
899
{
900
    add_open_file(TRUE);
901

902
    assert(open_files != NULL);
903

904
905
    /* If only one file buffer is open, indicate it on the statusbar and
     * get out. */
906
    if (open_files == open_files->next) {
907
	statusbar(_("No more open file buffers"));
908
	return;
909
910
    }

911
912
913
    /* Switch to the next or previous file, depending on the value of
     * next. */
    open_files = next ? open_files->next : open_files->prev;
914
915

#ifdef DEBUG
916
    fprintf(stderr, "filename is %s\n", open_files->filename);
917
918
#endif

919
    /* Load the file we switched to. */
920
921
    load_open_file();

922
    /* And indicate the switch on the statusbar. */
923
    statusbar(_("Switched to %s"),
924
      ((open_files->filename[0] == '\0') ? _("New Buffer") :
925
	open_files->filename));
926

927
928
929
930
931
#ifdef DEBUG
    dump_buffer(current);
#endif
}

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

939
940
/* Open the next entry in the open_files structure.  This function is
 * used by the shortcut list. */
941
void open_nextfile_void(void)
942
{
943
    open_prevnext_file(TRUE);
944
945
}

946
947
/* 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
948
 * there are no more open file buffers. */
949
bool close_open_file(void)
950
{
951
    assert(open_files != NULL);
952

953
954
    /* If only one file is open, get out. */
    if (open_files == open_files->next)
955
	return FALSE;
956

957
958
    /* Open the next file. */
    open_nextfile_void();
Chris Allegretta's avatar
Chris Allegretta committed
959

960
961
    /* Close the file we had open before. */
    unlink_opennode(open_files->prev);
962

963
    /* Reinitialize the shortcut list. */
964
    shortcut_init(FALSE);
965
    display_main_list();
966

967
    return TRUE;
968
}
969
#endif /* ENABLE_MULTIBUFFER */
970

971
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
972
/* When passed "[relative path]" or "[relative path][filename]" in
973
 * origpath, return "[full path]" or "[full path][filename]" on success,
974
975
976
977
 * 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. */
978
char *get_full_path(const char *origpath)
979
{
980
    char *d_here, *d_there = NULL;
981

982
983
    if (origpath == NULL)
    	return NULL;
984

985
986
987
988
989
990
991
992
993
994
    /* Get the current directory. */
#if PATH_MAX != -1
    d_here = charalloc(PATH_MAX + 1);
#else
    d_here = NULL;
#endif
    d_here = getcwd(d_here, PATH_MAX + 1);
#if PATH_MAX != -1
    align(&d_here);
#endif
995

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

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

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

	    if (d_there[d_there_len - 1] != '/') {
		d_there = charealloc(d_there, d_there_len + 2);
1026
1027
1028
1029
		strcat(d_there, "/");
	    }
	}

1030
	/* Search for the last slash in d_there. */
1031
1032
	last_slash = strrchr(d_there, '/');

1033
1034
1035
1036
	/* 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);
1037

1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
	    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) {
1053
		free(d_there);
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
		d_there = NULL;
	    } else {
		/* Get the full path and save it in d_there. */
		free(d_there);
#if PATH_MAX != -1
		d_there = charalloc(PATH_MAX + 1);
#else
		d_there = NULL;
#endif
		d_there = getcwd(d_there, PATH_MAX + 1);
#if PATH_MAX != -1
1065
		align(&d_there);
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
#endif

		if (d_there == NULL)
		    /* 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;
		else 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, "/");
1077
		}
1078
1079
1080
1081

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    free(full_tempdir);
1169

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

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

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

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

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

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

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

1214
    assert(full_operating_dir != NULL);
1215
1216

    fullpath = get_full_path(currpath);
1217
1218

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

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

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

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

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

1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
/* Read from inn, write to out.  We assume inn is opened for reading,
 * and out for writing.  We return 0 on success, -1 on read error, -2 on
 * write error. */
int copy_file(FILE *inn, FILE *out)
{
    char buf[BUFSIZ];
    size_t charsread;
    int retval = 0;

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

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

1343
    assert(name != NULL);
1344
    if (name[0] == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1345
	return -1;
1346
1347
    if (!tmp)
	titlebar(NULL);
1348

1349
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1350

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

1360
    anyexists = (lstat(realname, &lst) != -1);
1361

1362
1363
1364
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
1365

1366
1367
1368
    /* 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)) {
1369
1370
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1371
1372
1373
	goto cleanup_and_exit;
    }

1374
    /* Save the state of file at the end of the symlink (if there is
1375
     * one). */
1376
    realexists = (stat(realname, &st) != -1);
1377

1378
1379
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1380
1381
1382
1383
     * 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. */
1384
    if (ISSET(BACKUP_FILE) && !tmp && realexists &&
1385
1386
1387
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1388
	FILE *backup_file;
1389
	char *backupname;
1390
	struct utimbuf filetime;
1391
	int copy_status;
1392

1393
	/* Save the original file's access and modification times. */
1394
1395
1396
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1397
	/* Open the original file to copy to the backup. */
1398
	f = fopen(realname, "rb");
1399
	if (f == NULL) {
1400
	    statusbar(_("Error reading %s: %s"), realname,
1401
		strerror(errno));
1402
	    goto cleanup_and_exit;
1403
1404
	}

1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
	/* 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);
	}
1437

1438
1439
1440
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1441
	backup_file = fopen(backupname, "wb");
1442
1443
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1444
1445
	    statusbar(_("Error writing %s: %s"), backupname,
		strerror(errno));
1446
	    free(backupname);
1447
1448
1449
1450
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1451
1452
1453
	}

#ifdef DEBUG
1454
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1455
1456
#endif

1457
1458
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1459

1460
	/* And set metadata. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1461
1462
1463
	if (copy_status != 0 || chown(backupname,
		originalfilestat.st_uid, originalfilestat.st_gid) == -1
		|| utime(backupname, &filetime) == -1) {
1464
1465
	    free(backupname);
	    if (copy_status == -1)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1466
1467
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1468
1469
1470
1471
1472
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1473
1474
	free(backupname);
    }
1475
#endif /* !NANO_SMALL */
1476

1477
1478
1479
1480
1481
1482
    /* If NOFOLLOW_SYMLINKS and the file is a link, we aren't doing
     * prepend or append.  So we delete the link first, and just
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1483
	goto cleanup_and_exit;
1484
    }
1485

1486
1487
    original_umask = umask(0);
    umask(original_umask);
1488

1489
    /* If we create a temp file, we don't let anyone else access it.  We
1490
     * create a temp file if tmp is TRUE or if we're prepending. */
1491
1492
1493
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

1494
    /* If we're prepending, copy the file to a temp file. */
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
    if (append == 2) {
	int fd_source;
	FILE *f_source = NULL;

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

1516
1517
1518
1519
1520
1521
1522
	fd_source = open(realname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
	}
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1523
1524
	    statusbar(_("Error reading %s: %s"), realname,
		strerror(errno));
1525
1526
1527
1528
1529
1530
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

	if (copy_file(f_source, f) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1531
1532
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1533
	    unlink(tempname);
1534
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1535
1536
1537
	}
    }

1538
1539
    /* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This is
     * now copied from joe, because wiggy says so *shrug*. */
1540
1541
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
1542
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1543

1544
    /* Set the umask back to the user's original value. */
1545
1546
1547
1548
1549
    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));
1550
1551
1552
	/* tempname has been set only if we're prepending. */
	if (tempname != NULL)
	    unlink(tempname);
1553
1554
	goto cleanup_and_exit;
    }
1555

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1556
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1557
    if (f == NULL) {
1558
1559
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1560
	goto cleanup_and_exit;
1561
1562
    }

1563
    /* There might not be a magicline.  There won't be when writing out
1564
1565
1566
1567
1568
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1569

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

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

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

1578
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1579
1580
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1581
	    fclose(f);
1582
	    goto cleanup_and_exit;
1583
	}
1584
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1585
	if (fmt == DOS_FILE || fmt == MAC_FILE) {
1586
	    if (putc('\r', f) == EOF) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1587
1588
		statusbar(_("Error writing %s: %s"), realname,
			strerror(errno));
1589
1590
1591
		fclose(f);
		goto cleanup_and_exit;
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1592
	}
Chris Allegretta's avatar
Chris Allegretta committed
1593

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

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

1610
    /* If we're prepending, open the temp file, and append it to f. */
1611
    if (append == 2) {
1612
1613
1614
1615
1616
1617
1618
1619
	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);
1620
	}
1621
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1622
1623
	    statusbar(_("Error reading %s: %s"), tempname,
		strerror(errno));
1624
	    fclose(f);
1625
	    goto cleanup_and_exit;
1626
1627
	}

1628
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1629
1630
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1631
	    goto cleanup_and_exit;
1632
	}
1633
1634
1635
1636
    } 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
1637
    }
1638

1639
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1640
	if (!nonamechange) {
1641
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1642
1643
#ifdef ENABLE_COLOR
	    update_color();
1644
1645
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1646
1647
#endif
	}
1648

1649
1650
1651
1652
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1653
1654
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1655
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1656
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1657
    }
1658
1659
1660
1661
1662

    retval = 1;

  cleanup_and_exit:
    free(realname);
1663
    free(tempname);
1664
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1665
1666
}

1667
1668
1669
#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
1670
1671
1672
1673
 * 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. */
1674
int write_marked(const char *name, bool tmp, int append)
1675
1676
{
    int retval = -1;
1677
    bool old_modified = ISSET(MODIFIED);
1678
	/* write_file() unsets the MODIFIED flag. */
1679
1680
1681
1682
1683
1684
1685
1686
    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,
1687
	(const filestruct **)&bot, &bot_x, NULL);
1688
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1689
1690

    /* If the line at filebot is blank, treat it as the magicline and
1691
1692
1693
1694
1695
     * 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();
1696

1697
    retval = write_file(name, tmp, append, TRUE);
1698

1699
1700
1701
1702
1703
1704
    /* If we added a magicline, remove it now. */
    if (added_magicline)
	remove_magicline();

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

1707
    if (old_modified)
1708
1709
1710
1711
1712
1713
	set_modified();

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

1714
int do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1715
{
1716
    int i;
1717
1718
1719
    int retval = 0, append = 0;
    char *ans;
	/* The last answer the user typed on the statusbar. */
1720
#ifdef NANO_EXTRA
1721
    static bool did_cred = FALSE;
1722
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1723

1724
    currshortcut = writefile_list;
1725

1726
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1727
1728
1729
1730
1731
	retval = write_file(filename, FALSE, 0, FALSE);

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

1734
#ifndef NANO_SMALL
1735
    if (ISSET(MARK_ISSET) && !exiting)
1736
	ans = mallocstrcpy(NULL, "");
1737
1738
    else
#endif
1739
	ans = mallocstrcpy(NULL, filename);
1740
1741
1742
1743

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

1746
	if (fmt == DOS_FILE)
1747
	   formatstr = N_(" [DOS Format]");
1748
	else if (fmt == MAC_FILE)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1749
	   formatstr = N_(" [Mac Format]");
1750
1751
1752
	else
	   formatstr = "";

1753
	if (ISSET(BACKUP_FILE))
1754
	   backupstr = N_(" [Backup]");
1755
1756
1757
	else
	   backupstr = "";

1758
	/* Be nice to the translation folks. */
1759
	if (ISSET(MARK_ISSET) && !exiting) {
1760
	    if (append == 2)
1761
		msg = N_("Prepend Selection to File");
Chris Allegretta's avatar
Chris Allegretta committed
1762
	    else if (append == 1)
1763
		msg = N_("Append Selection to File");
1764
	    else
1765
		msg = N_("Write Selection to File");
1766
1767
	} else
#endif /* !NANO_SMALL */
1768
	if (append == 2)
1769
	    msg = N_("File Name to Prepend to");
Chris Allegretta's avatar
Chris Allegretta committed
1770
	else if (append == 1)
1771
	    msg = N_("File Name to Append to");
1772
	else
1773
	    msg = N_("File Name to Write");
1774

1775
1776
1777
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
1778
	i = statusq(!ISSET(RESTRICTED) || filename[0] == '\0',
1779
		writefile_list, ans,
1780
#ifndef NANO_SMALL
1781
		NULL, "%s%s%s", _(msg), formatstr, backupstr
1782
#else
1783
		"%s", _(msg)
1784
1785
1786
#endif
		);

1787
	if (i < 0) {
1788
	    statusbar(_("Cancelled"));
1789
1790
1791
1792
	    retval = -1;
	    break;
	} else {
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
1793

1794
#ifndef DISABLE_BROWSER
1795
1796
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
1797

1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
		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
1809
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1810
#ifndef NANO_SMALL
1811
	    if (i == TOGGLE_DOS_KEY) {
1812
		fmt = (fmt == DOS_FILE) ? NIX_FILE : DOS_FILE;
1813
1814
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1815
		fmt = (fmt == MAC_FILE) ? NIX_FILE : MAC_FILE;
1816
1817
1818
1819
1820
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1821
#endif /* !NANO_SMALL */
1822
1823
1824
1825
1826
1827
1828
	    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
1829

Chris Allegretta's avatar
Chris Allegretta committed
1830
#ifdef DEBUG
1831
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1832
#endif
1833
1834

#ifdef NANO_EXTRA
1835
1836
1837
1838
1839
1840
1841
	    if (exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0 && !did_cred) {
		do_credits();
		did_cred = TRUE;
		retval = -1;
		break;
	    }
1842
#endif
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
	    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'
1856
#ifndef NANO_SMALL
1857
			&& (exiting || !ISSET(MARK_ISSET))
1858
#endif
1859
			) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1860
1861
		    i = do_yesno(FALSE,
			_("Save file under DIFFERENT NAME ? "));
1862
1863
1864
		    if (i == 0 || i == -1)
			continue;
		}
1865
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1866

1867
#ifndef NANO_SMALL
1868
1869
1870
1871
1872
1873
1874
	    /* 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
1875
#endif /* !NANO_SMALL */
1876
		retval = write_file(answer, FALSE, append, FALSE);
1877

1878
#ifdef ENABLE_MULTIBUFFER
1879
1880
1881
1882
	    /* If we're not about to exit, update the current entry in
	     * the open_files structure. */
	    if (!exiting)
		add_open_file(TRUE);
1883
#endif
1884
1885
1886

	    break;
	}
1887
    } /* while (TRUE) */
1888
1889

    free(ans);
1890

1891
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1892
1893
}

1894
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1895
{
1896
    do_writeout(FALSE);
1897
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1898
}
Chris Allegretta's avatar
Chris Allegretta committed
1899

1900
1901
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
1902
char *real_dir_from_tilde(const char *buf)
1903
{
Chris Allegretta's avatar
Chris Allegretta committed
1904
    char *dirtmp = NULL;
1905

1906
1907
1908
    if (buf == NULL)
    	return NULL;

1909
    if (buf[0] == '~') {
1910
	size_t i;
1911
	const char *tilde_dir;
1912

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

1917
1918
1919
1920
1921
1922
1923
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
	    tilde_dir = homedir;
	} else {
	    const struct passwd *userdata;

1924
1925
1926
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1927
		strncmp(userdata->pw_name, buf + 1, i - 1) != 0);
1928
1929
	    endpwent();
	    tilde_dir = userdata->pw_dir;
Chris Allegretta's avatar
Chris Allegretta committed
1930
	}
1931

1932
1933
1934
	if (tilde_dir != NULL) {
	    dirtmp = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	    sprintf(dirtmp, "%s%s", tilde_dir, buf + i);
1935
	}
1936
    }
1937

1938
1939
    /* Set a default value for dirtmp, in case the user's home directory
     * isn't found. */
1940
    if (dirtmp == NULL)
1941
	dirtmp = mallocstrcpy(NULL, buf);
1942

1943
    return dirtmp;
1944
1945
}

1946
#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
1947
1948
/* Our sort routine for file listings.  Sort alphabetically and
 * case-insensitively, and sort directories before filenames. */
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
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
1966
#ifndef DISABLE_TABCOMP
1967
1968
/* Is the given file a directory? */
int is_dir(const char *buf)
1969
{
1970
    char *dirptr = real_dir_from_tilde(buf);
1971
1972
    struct stat fileinfo;

1973
1974
    int ret = (stat(dirptr, &fileinfo) != -1 &&
		S_ISDIR(fileinfo.st_mode));
1975

1976
    assert(buf != NULL && dirptr != buf);
1977

1978
    free(dirptr);
1979

1980
    return ret;
1981
}
Chris Allegretta's avatar
Chris Allegretta committed
1982

1983
/* These functions (username_tab_completion(), cwd_tab_completion(), and
Chris Allegretta's avatar
Chris Allegretta committed
1984
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1985
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
 *
 * 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.
1998
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
1999

2000
2001
2002
2003
/* 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
2004
{
2005
2006
    char **matches = NULL;
    const struct passwd *userdata;
2007

2008
    assert(buf != NULL && num_matches != NULL && buflen > 0);
2009

2010
    *num_matches = 0;
2011

2012
    while ((userdata = getpwent()) != NULL) {
2013
2014
2015
	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... */
2016

2017
2018
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2019
2020
2021
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2022
2023
#endif

2024
2025
2026
2027
2028
	    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);
2029
	    ++(*num_matches);
2030
	}
2031
2032
    }
    endpwent();
2033

2034
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2035
2036
2037
}

/* This was originally called exe_n_cwd_tab_completion, but we're not
2038
2039
2040
 * 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
2041
{
2042
2043
2044
2045
2046
2047
2048
    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
2049
    DIR *dir;
2050
    const struct dirent *next;
Chris Allegretta's avatar
Chris Allegretta committed
2051

2052
    assert(dirname != NULL && num_matches != NULL && buflen >= 0);
2053

2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
    *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
2068
    } else {
2069
2070
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2071
2072
    }

2073
    assert(dirname[strlen(dirname) - 1] == '/');
2074

Chris Allegretta's avatar
Chris Allegretta committed
2075
    dir = opendir(dirname);
2076

2077
    if (dir == NULL) {
2078
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2079
	beep();
2080
2081
2082
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2083
    }
2084
2085
2086
2087
2088
2089

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

Chris Allegretta's avatar
Chris Allegretta committed
2090
2091
2092
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2093
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2094
#endif
2095
2096
2097
2098
2099
2100
	/* See if this matches. */
	if (strncmp(next->d_name, filename, filenamelen) == 0 &&
		(*filename == '.' || (strcmp(next->d_name, ".") != 0 &&
		strcmp(next->d_name, "..") != 0))) {
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2101
2102
2103

#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
	     * 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) +
		strlen(next->d_name) + 1);

	    sprintf(tmp2, "%s%s", dirname, next->d_name);
	    if (check_operating_dir(tmp2, TRUE)) {
		free(tmp2);
		continue;
2115
	    }
2116
	    free(tmp2);
2117
2118
#endif

2119
2120
2121
2122
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
	    matches[*num_matches] = mallocstrcpy(NULL, next->d_name);
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2123
2124
	}
    }
2125
2126
    closedir(dir);
    free(dirname);
2127
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2128

2129
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2130
2131
}

2132
2133
2134
/* 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
2135
{
2136
2137
    size_t num_matches = 0;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2138

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

2141
    *list = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2142

2143
2144
2145
2146
    /* 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
2147

2148
2149
2150
2151
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2152

2153
2154
2155
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
	matches = cwd_tab_completion(buf, &num_matches, *place);
2156

2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
    if (num_matches <= 0)
	beep();
    else {
	size_t match, common_len = 0;
	size_t lastslash = strrchrn(buf, '/', *place);
	char *mzero;

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

2171
	    if (match < num_matches || matches[0][common_len] == '\0')
2172
		break;
2173

2174
2175
	    common_len++;
	}
2176

2177
2178
2179
	mzero = charalloc(lastslash + common_len + 1);
	sprintf(mzero, "%.*s%.*s", lastslash, buf, common_len,
		matches[0]);
2180

2181
	common_len += lastslash;
2182

2183
	assert(common_len >= *place);
2184

2185
2186
2187
2188
2189
	if (num_matches == 1 && is_dir(mzero)) {
	    mzero[common_len] = '/';
	    common_len++;
	    assert(common_len > *place);
	}
2190

2191
2192
2193
	if (num_matches > 1 && (common_len != *place ||
		*lastwastab == FALSE))
	    beep();
2194

2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
	/* If there is more match to display on the statusbar, show it.
	 * We reset lastwastab to FALSE: it requires hitting Tab twice
	 * in succession with no statusbar changes to see a match
	 * list. */
	if (common_len != *place) {
	    size_t buflen = strlen(buf);

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

2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
	    /* 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;
2223
2224
		    break;
		}
2225
2226
		if (common_len > longest_name)
		    longest_name = common_len;
2227
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2228

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

2231
2232
2233
2234
	    /* 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);
2235

2236
2237
2238
2239
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2240

2241
2242
	    /* Disable el cursor. */
	    curs_set(0);
2243

2244
2245
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2246

2247
2248
		wmove(edit, editline, (longest_name + 2) *
			(match % columns));
2249

2250
2251
2252
2253
2254
		if (match % columns == 0 && editline == editwinrows - 1
			&& num_matches - match > columns) {
		    waddstr(edit, _("(more)"));
		    break;
		}
2255

2256
2257
2258
2259
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2260

2261
		if ((match + 1) % columns == 0)
2262
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2263
2264
	    }
	    wrefresh(edit);
2265
	    *list = TRUE;
2266
2267
2268
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2269
2270
    }

2271
2272
    free_charptrarray(matches, num_matches);

2273
    /* Only refresh the edit window if we don't have a list of filename
2274
     * matches on it. */
2275
    if (*list == FALSE)
2276
	edit_refresh();
2277
2278

    /* Enable el cursor. */
2279
    curs_set(1);
2280

2281
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2282
}
2283
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2284

2285
2286
/* Only print the last part of a path.  Isn't there a shell command for
 * this? */
2287
2288
const char *tail(const char *foo)
{
2289
    const char *tmp = strrchr(foo, '/');
2290

2291
2292
2293
    if (tmp == NULL)
	tmp = foo;
    else if (*tmp == '/')
2294
2295
2296
2297
2298
	tmp++;

    return tmp;
}

2299
#ifndef DISABLE_BROWSER
2300
/* Free our malloc()ed memory. */
2301
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2302
{
2303
2304
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2305
2306
2307
    free(array);
}

2308
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2309
2310
void striponedir(char *foo)
{
2311
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2312

2313
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2314

2315
2316
2317
    tmp = strrchr(foo, '/');
    if (tmp != NULL)
 	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2318
2319
}

2320
2321
2322
2323
2324
2325
2326
int readable_dir(const char *path)
{
    DIR *dir = opendir(path);

    /* If dir is NULL, don't do closedir(), since that changes errno. */
    if (dir != NULL)
	closedir(dir);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2327
    return (dir != NULL);
2328
2329
}

2330
/* Initialize the browser code, including the list of files in *path */
2331
char **browser_init(const char *path, int *longest, int *numents)
2332
2333
2334
{
    DIR *dir;
    struct dirent *next;
2335
    char **filelist;
2336
    int i = 0;
2337
    size_t path_len;
2338
2339

    dir = opendir(path);
2340
    if (dir == NULL)
2341
2342
2343
2344
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
2345
	if (strcmp(next->d_name, ".") == 0)
2346
2347
2348
2349
2350
2351
2352
2353
	   continue;
	(*numents)++;
	if (strlen(next->d_name) > *longest)
	    *longest = strlen(next->d_name);
    }
    rewinddir(dir);
    *longest += 10;

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

2356
    if (strcmp(path, "/") == 0)
2357
2358
2359
	path = "";
    path_len = strlen(path);

2360
    while ((next = readdir(dir)) != NULL) {
2361
	if (strcmp(next->d_name, ".") == 0)
2362
2363
	   continue;

2364
2365
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2366
2367
	i++;
    }
2368
    closedir(dir);
2369
2370
2371
2372
2373
2374
2375

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2376
/* Our browser function.  inpath is the path to start browsing from */
2377
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2378
2379
2380
2381
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2382
2383
2384
    int numents = 0, i = 0, j = 0, longest = 0, abort = 0, col = 0;
    int selected = 0, editline = 0, width = 0, filecols = 0, lineno = 0;
    int kbinput = ERR;
2385
    bool meta_key, func_key;
2386
    char **filelist = (char **)NULL;
2387
#ifndef DISABLE_MOUSE
2388
2389
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2390

2391
2392
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2393
2394
2395
    /* If path isn't the same as inpath, we are being passed a new
	dir as an arg.  We free it here so it will be copied from 
	inpath below */
2396
    if (path != NULL && strcmp(path, inpath) != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2397
2398
2399
2400
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2401
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2402
    if (path == NULL)
2403
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2404
2405

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

2408
    /* Sort the list. */
Chris Allegretta's avatar
Chris Allegretta committed
2409
2410
2411
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2412
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2413
2414
2415
2416
2417
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2418
2419

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2420
    do {
2421
2422
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2423

2424
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2425

2426
	currshortcut = browser_list;
2427

Chris Allegretta's avatar
Chris Allegretta committed
2428
2429
 	editline = 0;
	col = 0;
2430
	    
2431
	/* Compute line number we're on now, so we don't divide by zero later */
2432
2433
2434
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2435
2436

	switch (kbinput) {
2437

2438
#ifndef DISABLE_MOUSE
2439
	case KEY_MOUSE:
2440
	    if (getmouse(&mevent) == ERR)
2441
		return retval;
2442
2443
2444
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2445
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2446
2447
2448
2449
		int selectedbackup = selected;

		mevent.y -= 2;

2450
2451
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2452
		selected = (lineno / editwinrows) * editwinrows * width
2453
2454
2455
2456
2457
2458
			+ 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--;
2459
2460
2461

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2462
		if (selected > numents - 1)
2463
		    selected = numents - 1;
2464
		else if (selectedbackup == selected)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2465
		    /* Put back the 'select' key */
2466
		    unget_kbinput('s', FALSE, FALSE);
2467
2468
	    } else {
		/* Must be clicking a shortcut */
2469
2470
2471
		int mouse_x, mouse_y;
		get_mouseinput(&mouse_x, &mouse_y, TRUE);
	    }
2472

2473
2474
            break;
#endif
2475
	case NANO_PREVLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2476
2477
2478
	    if (selected - width >= 0)
		selected -= width;
	    break;
2479
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2480
2481
2482
	    if (selected > 0)
		selected--;
	    break;
2483
	case NANO_NEXTLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2484
2485
2486
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
2487
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2488
2489
2490
2491
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2492
	case NANO_PREVPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2493
	case '-': /* Pico compatibility */
2494
	    if (selected >= (editwinrows + lineno % editwinrows) * width)
2495
		selected -= (editwinrows + lineno % editwinrows) * width;
Chris Allegretta's avatar
Chris Allegretta committed
2496
2497
2498
2499
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2500
	case NANO_NEXTPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2501
	case ' ': /* Pico compatibility */
2502
2503
	    selected += (editwinrows - lineno % editwinrows) * width;
	    if (selected >= numents)
Chris Allegretta's avatar
Chris Allegretta committed
2504
2505
		selected = numents - 1;
	    break;
2506
2507
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2508
	case '?': /* Pico compatibility */
2509
#ifndef DISABLE_HELP
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2510
	    do_help();
2511
	    curs_set(0);
2512
2513
2514
#else
	    nano_disabled_msg();
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2515
	    break;
2516
	case NANO_ENTER_KEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2517
2518
	case 'S': /* Pico compatibility */
	case 's':
Chris Allegretta's avatar
Chris Allegretta committed
2519
	    /* You can't cd up from / */
2520
2521
	    if (strcmp(filelist[selected], "/..") == 0 &&
		strcmp(path, "/") == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2522
		statusbar(_("Can't move up a directory"));
2523
		beep();
Rocco Corsi's avatar
   
Rocco Corsi committed
2524
2525
2526
		break;
	    }

2527
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2528
2529
2530
	    /* Note: the selected file can be outside the operating
	     * directory if it is .. or if it is a symlink to 
	     * directory outside the operating directory. */
2531
	    if (check_operating_dir(filelist[selected], FALSE)) {
2532
2533
		statusbar(_("Can't go outside of %s in restricted mode"),
			operating_dir);
2534
2535
		beep();
		break;
2536
2537
2538
	    }
#endif

2539
	    if (stat(filelist[selected], &st) == -1) {
2540
2541
		statusbar(_("Can't open \"%s\": %s"), filelist[selected],
			strerror(errno));
2542
2543
2544
		beep();
		break;
	    }
2545

2546
2547
2548
2549
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2550
2551
	    }

2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
	    new_path = mallocstrcpy(NULL, filelist[selected]);

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

2566
2567
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
2568
2569
		statusbar(_("Can't open \"%s\": %s"), new_path,
			strerror(errno));
2570
2571
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2572
	    }
2573
2574
2575
2576
2577
2578
2579

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

2580
	/* Go to a specific directory */
2581
2582
	case NANO_GOTOLINE_KEY:
	case NANO_GOTOLINE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2583
2584
	case 'G': /* Pico compatibility */
	case 'g':
Rocco Corsi's avatar
   
Rocco Corsi committed
2585
	    curs_set(1);
2586
	    j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2587
#ifndef NANO_SMALL
2588
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2589
#endif
2590
		_("Go To Directory"));
2591
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2592
2593
2594
	    curs_set(0);

	    if (j < 0) {
2595
		statusbar(_("Cancelled"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2596
2597
2598
		break;
	    }

2599
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2600

2601
2602
2603
	    if (new_path[0] != '/') {
		new_path = charealloc(new_path, strlen(path) + strlen(answer) + 2);
		sprintf(new_path, "%s/%s", path, answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2604
2605
	    }

2606
#ifndef DISABLE_OPERATINGDIR
2607
	    if (check_operating_dir(new_path, FALSE)) {
2608
2609
2610
2611
2612
2613
2614
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		free(new_path);
		break;
	    }
#endif

	    if (!readable_dir(new_path)) {
Rocco Corsi's avatar
   
Rocco Corsi committed
2615
2616
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2617
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2618
		break;
2619
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2620
2621

	    /* Start over again with the new path value */
2622
2623
	    free_charptrarray(filelist, numents);
	    free(foo);
2624
2625
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2626
2627
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2628
	/* Stuff we want to abort the browser */
2629
	case NANO_CANCEL_KEY:
2630
	case NANO_EXIT_KEY:
2631
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2632
2633
	case 'E': /* Pico compatibility */
	case 'e':
2634
2635
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2636
2637
2638
2639
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2640
2641
	blank_edit();

2642
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
	    i = width * editwinrows * ((selected / width) / editwinrows);
	else
	    i = 0;

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

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

	    /* Put file info in the string also */
2657
2658
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2659
2660
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2661
2662
2663
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2664
2665
2666
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
2667
		    stat(filelist[j], &st);
2668
2669
2670
2671
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2672
2673
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2674
			(int) st.st_size);
2675
2676
2677
2678
2679
2680
		else if (st.st_size >= (1 << 30)) /* at least 1 gig */
		    sprintf(foo + longest - 7, "%4d GB", 
			(int) st.st_size >> 30);
		else if (st.st_size >= (1 << 20)) /* at least 1 meg */
		    sprintf(foo + longest - 7, "%4d MB", 
			(int) st.st_size >>     20);
2681
		else /* It's more than 1 k and less than a meg */
2682
2683
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2684
2685
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2686
	    /* Highlight the currently selected file/dir */
2687
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2688
		wattron(edit, A_REVERSE);
2689
2690
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2691
2692
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2693
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2694
2695
2696
2697
2698
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2699
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2700
2701
2702
2703
2704
2705
2706
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
2707
	wrefresh(edit);
2708
2709
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
Chris Allegretta's avatar
Chris Allegretta committed
2710
2711
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2712
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2713
2714
    edit_refresh();

Chris Allegretta's avatar
Chris Allegretta committed
2715
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2716
2717
2718
2719
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2720

2721
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2722
 starts do_browser from there, else from the current dir */
2723
char *do_browse_from(const char *inpath)
2724
2725
{
    struct stat st;
2726
    char *bob;
2727
2728
2729
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2730

2731
2732
2733
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2734

2735
2736
2737
2738
2739
2740
2741
2742
    /*
     * Perhaps path is a directory.  If so, we will pass that to
     * do_browser.  Otherwise, perhaps path is a directory / a file.  So
     * we try stripping off the last path element.  If it still isn't a
     * directory, just use the current directory. */

    if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	striponedir(path);
2743
2744
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2745
	    path = getcwd(NULL, PATH_MAX + 1);
2746
	}
2747
    }
2748

2749
2750
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2751
    if (check_operating_dir(path, FALSE))
2752
2753
2754
2755
2756
2757
2758
2759
2760
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2761
    return bob;
2762
}
Chris Allegretta's avatar
Chris Allegretta committed
2763
#endif /* !DISABLE_BROWSER */
2764

2765
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2766
2767
2768
/* 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)
2769
{
2770
    char *nanohist = NULL;
2771

2772
2773
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2774

2775
2776
2777
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2778
    }
2779
2780
2781
2782
2783
2784
    return nanohist;
}

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

2786
    /* assume do_rcfile() has reported missing home dir */
2787
2788
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2789

2790
	if (hist == NULL) {
2791
	    if (errno != ENOENT) {
2792
2793
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2794
		rcfile_error(N_("Error reading %s: %s"), nanohist, strerror(errno));
2795
2796
2797
		fprintf(stderr, _("\nPress Return to continue starting nano\n"));
		while (getchar() != '\n')
		    ;
2798
	    }
2799
	} else {
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
	    historyheadtype *history = &search_history;
	    char *line = NULL;
	    size_t buflen = 0;
	    ssize_t read;

	    while ((read = getline(&line, &buflen, hist)) >= 0) {
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
		    update_history(history, line);
		} else
2814
2815
2816
		    history = &replace_history;
	    }
	    fclose(hist);
2817
	    free(line);
2818
2819
	    UNSET(HISTORY_CHANGED);
	}
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
	free(nanohist);
    }
}

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

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

	sunder(h->data);
	if (fwrite(h->data, sizeof(char), len, hist) < len ||
		putc('\n', hist) == EOF)
	    return FALSE;
2836
    }
2837
    return TRUE;
2838
2839
2840
2841
2842
}

/* save histories to ~/.nano_history */
void save_history(void)
{
2843
    char *nanohist;
2844
2845

    /* don't save unchanged or empty histories */
2846
    if ((search_history.count == 0 && replace_history.count == 0) ||
2847
	!ISSET(HISTORY_CHANGED) || ISSET(VIEW_MODE))
2848
2849
	return;

2850
2851
2852
2853
    nanohist = histfilename();

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

2855
	if (hist == NULL)
2856
	    rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2857
	else {
2858
2859
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2860
2861
2862
2863

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2864
		rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2865
2866
2867
2868
2869
	    fclose(hist);
	}
	free(nanohist);
    }
}
2870
#endif /* !NANO_SMALL && ENABLE_NANORC */