files.c 76 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
#include <ctype.h>
35
#include <pwd.h>
Chris Allegretta's avatar
Chris Allegretta committed
36
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
37
#include "proto.h"
Chris Allegretta's avatar
Chris Allegretta committed
38

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

252
#ifndef NANO_SMALL
253
254
255
    /* If file conversion isn't disabled and the last character in this
     * file is a CR, read it in properly as a Mac format line. */
    if (len == 0 && !ISSET(NO_CONVERT) && input == '\r') {
256
257
	len = 1;

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

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

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

280
281
282
283
    free(buf);

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

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

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

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

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

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

341
    assert(f != NULL);
342

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

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

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

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

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

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

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

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

405
406
407
    return buf;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

641
642
643
644
645
646
647
648
649
		/* If we didn't insert into a new buffer, and we were at
		 * the top of the edit window before, set the saved
		 * value of edittop to the new top of the edit window,
		 * and update the current y-coordinate to account for
		 * the number of lines inserted. */
		if (at_edittop)
		    edittop_save = fileage;
		current_y += current_y_save;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
650
		/* If we didn't insert into a new buffer, unpartition
651
652
653
654
		 * the filestruct so that it contains all the text
		 * again.  Note that we've replaced the non-text
		 * originally in the partition with the text in the
		 * inserted file/executed command output. */
655
		unpartition_filestruct(&filepart);
656

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

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

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

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

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

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

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

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

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

    display_main_list();
}

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

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

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

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

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

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

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

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

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

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

775
776
777
778
779
780
781
782
783
    /* If there are no entries in open_files, make the first one. */
    if (open_files == NULL) {
	open_files = make_new_opennode();
	splice_opennode(open_files, open_files, open_files);
    /* Otherwise, if we're not updating, make a new entry for
     * open_files and splice it in after the current entry. */
    } else if (!update) {
	splice_opennode(open_files, make_new_opennode(),
		open_files->next);
784
785
786
	open_files = open_files->next;
    }

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

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

795
796
797
798
799
800
801
802
803
    /* Save the current file buffer. */
    open_files->fileage = fileage;
    open_files->filebot = filebot;

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

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

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

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

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

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

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
824
#ifndef NANO_SMALL
825
826
827
828
829
    /* Save the current marking status and mark, if applicable. */
    if (ISSET(MARK_ISSET)) {
	open_files->flags |= MARK_ISSET;
	open_files->mark_beginbuf = mark_beginbuf;
	open_files->mark_beginx = mark_beginx;
Chris Allegretta's avatar
Chris Allegretta committed
830
831
    }

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

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

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

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

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

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

859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
    /* Restore the current top of the edit window. */
    edittop = open_files->edittop;

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

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

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

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

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

    /* Restore the current modification status. */
    if (open_files->flags & MODIFIED)
Chris Allegretta's avatar
Chris Allegretta committed
879
880
881
882
883
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

#ifndef NANO_SMALL
884
885
886
887
    /* Restore the current marking status and mark, if applicable. */
    if (open_files->flags & MARK_ISSET) {
	mark_beginbuf = open_files->mark_beginbuf;
	mark_beginx = open_files->mark_beginx;
Chris Allegretta's avatar
Chris Allegretta committed
888
889
890
	SET(MARK_ISSET);
    } else
	UNSET(MARK_ISSET);
891

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

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

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

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

910
    assert(open_files != NULL);
911

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

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

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

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

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

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

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

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

954
955
/* Delete an entry from the open_files filestruct.  After deletion of an
 * entry, the next entry is opened.  Return TRUE on success or FALSE if
956
 * there are no more open file buffers. */
957
bool close_open_file(void)
958
{
959
    assert(open_files != NULL);
960

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

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

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

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

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

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

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

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

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

1003
1004
	align(&d_here);

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

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

	assert(d_there != NULL);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1141
1142
1143
    /* If $TMPDIR is set and non-empty, set tempdir to it, run it
     * through get_full_path(), and save the result in full_tempdir.
     * Otherwise, leave full_tempdir set to NULL. */
1144
    TMPDIR_env = getenv("TMPDIR");
1145
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1146
	full_tempdir = check_writable_directory(TMPDIR_env);
1147

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

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

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

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

    free(full_tempdir);
1170

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

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

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

1184
1185
1186
    full_operating_dir = get_full_path(operating_dir);

    /* If get_full_path() failed or the operating directory is
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1187
     * inaccessible, unset operating_dir. */
1188
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1189
1190
1191
1192
1193
1194
1195
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1196
1197
1198
1199
1200
/* Check to see if we're inside the operating directory.  Return FALSE
 * if we are, or TRUE otherwise.  If allow_tabcomp is TRUE, allow
 * incomplete names that would be matches for the operating directory,
 * so that tab completion will work. */
bool check_operating_dir(const char *currpath, bool allow_tabcomp)
1201
{
1202
1203
1204
1205
    /* The char *full_operating_dir is global for mem cleanup.  It
     * should have already been initialized by init_operating_dir().
     * Also, a relative operating directory path will only be handled
     * properly if this is done. */
1206

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

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

1215
    assert(full_operating_dir != NULL);
1216
1217

    fullpath = get_full_path(currpath);
1218
1219

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

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

1233
1234
1235
1236
1237
    /* If both searches failed, we're outside the operating directory.
     * Otherwise, check the search results; if the full operating
     * directory path is not at the beginning of the full current path
     * (for normal usage) and vice versa (for tab completion, if we're
     * allowing it), we're outside the operating directory. */
1238
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1239
1240
	retval = TRUE;
    free(fullpath);
1241
1242

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

1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
#ifndef NANO_SMALL
void init_backup_dir(void)
{
    char *full_backup_dir;

    if (backup_dir == NULL)
	return;

    full_backup_dir = get_full_path(backup_dir);

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

1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
/* 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;
}

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

1344
    assert(name != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1345

1346
    if (name[0] == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1347
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1348

1349
1350
    if (!tmp)
	titlebar(NULL);
1351

1352
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1353

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

1363
    anyexists = (lstat(realname, &lst) != -1);
1364

1365
1366
1367
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
1368

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

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

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

1391
	FILE *backup_file;
1392
	char *backupname;
1393
	struct utimbuf filetime;
1394
	int copy_status;
1395

1396
	/* Save the original file's access and modification times. */
1397
1398
1399
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1400
	/* Open the original file to copy to the backup. */
1401
	f = fopen(realname, "rb");
1402
	if (f == NULL) {
1403
	    statusbar(_("Error reading %s: %s"), realname,
1404
		strerror(errno));
1405
	    goto cleanup_and_exit;
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
1437
1438
1439
	/* 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);
	}
1440

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

#ifdef DEBUG
1457
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1458
1459
#endif

1460
1461
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1462

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

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

1490
1491
    original_umask = umask(0);
    umask(original_umask);
1492

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

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

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

1520
1521
1522
1523
1524
1525
1526
	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
1527
1528
	    statusbar(_("Error reading %s: %s"), realname,
		strerror(errno));
1529
1530
1531
1532
1533
1534
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

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

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

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

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

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

1571
1572
1573
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1574

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

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

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

1583
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1584
1585
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1586
	    fclose(f);
1587
	    goto cleanup_and_exit;
1588
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1589

1590
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1591
	if (fmt == DOS_FILE || fmt == MAC_FILE) {
1592
	    if (putc('\r', f) == EOF) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1593
1594
		statusbar(_("Error writing %s: %s"), realname,
			strerror(errno));
1595
1596
1597
		fclose(f);
		goto cleanup_and_exit;
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1598
	}
Chris Allegretta's avatar
Chris Allegretta committed
1599

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

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

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

1634
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1635
1636
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1637
	    goto cleanup_and_exit;
1638
	}
1639
1640
1641
1642
    } 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
1643
    }
1644

1645
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1646
	if (!nonamechange) {
1647
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1648
1649
#ifdef ENABLE_COLOR
	    update_color();
1650
	    if (!ISSET(NO_COLOR_SYNTAX))
1651
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1652
1653
#endif
	}
1654

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

    retval = 1;

  cleanup_and_exit:
    free(realname);
1669
    free(tempname);
1670
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1671
1672
}

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

    /* If the line at filebot is blank, treat it as the magicline and
1697
1698
1699
1700
1701
     * 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();
1702

1703
    retval = write_file(name, tmp, append, TRUE);
1704

1705
1706
1707
1708
1709
1710
    /* If we added a magicline, remove it now. */
    if (added_magicline)
	remove_magicline();

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

1713
    if (old_modified)
1714
1715
1716
1717
1718
1719
	set_modified();

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

1720
int do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1721
{
1722
    int i;
1723
1724
1725
    int retval = 0, append = 0;
    char *ans;
	/* The last answer the user typed on the statusbar. */
1726
#ifdef NANO_EXTRA
1727
    static bool did_cred = FALSE;
1728
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1729

1730
    currshortcut = writefile_list;
1731

1732
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1733
1734
1735
1736
1737
	retval = write_file(filename, FALSE, 0, FALSE);

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

1740
#ifndef NANO_SMALL
1741
    if (ISSET(MARK_ISSET) && !exiting)
1742
	ans = mallocstrcpy(NULL, "");
1743
1744
    else
#endif
1745
	ans = mallocstrcpy(NULL, filename);
1746
1747
1748
1749

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

1752
	if (fmt == DOS_FILE)
1753
	   formatstr = N_(" [DOS Format]");
1754
	else if (fmt == MAC_FILE)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1755
	   formatstr = N_(" [Mac Format]");
1756
1757
1758
	else
	   formatstr = "";

1759
	if (ISSET(BACKUP_FILE))
1760
	   backupstr = N_(" [Backup]");
1761
1762
1763
	else
	   backupstr = "";

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

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

1793
	if (i < 0) {
1794
	    statusbar(_("Cancelled"));
1795
1796
1797
1798
	    retval = -1;
	    break;
	} else {
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
1799

1800
#ifndef DISABLE_BROWSER
1801
1802
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
1803

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

Chris Allegretta's avatar
Chris Allegretta committed
1836
#ifdef DEBUG
1837
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1838
#endif
1839
1840

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

1873
#ifndef NANO_SMALL
1874
1875
1876
1877
1878
1879
1880
	    /* 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
1881
#endif /* !NANO_SMALL */
1882
		retval = write_file(answer, FALSE, append, FALSE);
1883

1884
#ifdef ENABLE_MULTIBUFFER
1885
1886
1887
1888
	    /* If we're not about to exit, update the current entry in
	     * the open_files structure. */
	    if (!exiting)
		add_open_file(TRUE);
1889
#endif
1890
1891
1892

	    break;
	}
1893
    } /* while (TRUE) */
1894
1895

    free(ans);
1896

1897
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1898
1899
}

1900
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1901
{
1902
    do_writeout(FALSE);
1903
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1904
}
Chris Allegretta's avatar
Chris Allegretta committed
1905

1906
1907
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
1908
char *real_dir_from_tilde(const char *buf)
1909
{
Chris Allegretta's avatar
Chris Allegretta committed
1910
    char *dirtmp = NULL;
1911

1912
1913
1914
    if (buf == NULL)
    	return NULL;

1915
    if (buf[0] == '~') {
1916
	size_t i;
1917
	const char *tilde_dir;
1918

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

1923
1924
1925
1926
1927
1928
1929
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
	    tilde_dir = homedir;
	} else {
	    const struct passwd *userdata;

1930
1931
1932
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1933
		strncmp(userdata->pw_name, buf + 1, i - 1) != 0);
1934
1935
	    endpwent();
	    tilde_dir = userdata->pw_dir;
Chris Allegretta's avatar
Chris Allegretta committed
1936
	}
1937

1938
1939
1940
	if (tilde_dir != NULL) {
	    dirtmp = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	    sprintf(dirtmp, "%s%s", tilde_dir, buf + i);
1941
	}
1942
    }
1943

1944
1945
    /* Set a default value for dirtmp, in case the user's home directory
     * isn't found. */
1946
    if (dirtmp == NULL)
1947
	dirtmp = mallocstrcpy(NULL, buf);
1948

1949
    return dirtmp;
1950
1951
}

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

1979
1980
    int ret = (stat(dirptr, &fileinfo) != -1 &&
		S_ISDIR(fileinfo.st_mode));
1981

1982
    assert(buf != NULL && dirptr != buf);
1983

1984
    free(dirptr);
1985

1986
    return ret;
1987
}
Chris Allegretta's avatar
Chris Allegretta committed
1988

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

2006
2007
2008
2009
/* 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
2010
{
2011
2012
    char **matches = NULL;
    const struct passwd *userdata;
2013

2014
    assert(buf != NULL && num_matches != NULL && buflen > 0);
2015

2016
    *num_matches = 0;
2017

2018
    while ((userdata = getpwent()) != NULL) {
2019
2020
2021
	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... */
2022

2023
2024
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2025
2026
2027
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2028
2029
#endif

2030
2031
2032
2033
2034
	    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);
2035
	    ++(*num_matches);
2036
	}
2037
2038
    }
    endpwent();
2039

2040
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2041
2042
2043
}

/* This was originally called exe_n_cwd_tab_completion, but we're not
2044
2045
2046
 * 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
2047
{
2048
2049
2050
2051
2052
2053
2054
    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
2055
    DIR *dir;
2056
    const struct dirent *next;
Chris Allegretta's avatar
Chris Allegretta committed
2057

2058
    assert(dirname != NULL && num_matches != NULL && buflen >= 0);
2059

2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
    *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
2074
    } else {
2075
2076
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2077
2078
    }

2079
    assert(dirname[strlen(dirname) - 1] == '/');
2080

Chris Allegretta's avatar
Chris Allegretta committed
2081
    dir = opendir(dirname);
2082

2083
    if (dir == NULL) {
2084
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2085
	beep();
2086
2087
2088
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2089
    }
2090
2091
2092
2093
2094
2095

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

Chris Allegretta's avatar
Chris Allegretta committed
2096
2097
2098
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2099
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2100
#endif
2101
2102
2103
2104
2105
2106
	/* 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... */
2107
2108
2109

#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
	     * 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;
2121
	    }
2122
	    free(tmp2);
2123
2124
#endif

2125
2126
2127
2128
	    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
2129
2130
	}
    }
2131
2132
    closedir(dir);
    free(dirname);
2133
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2134

2135
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2136
2137
}

2138
2139
2140
/* 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
2141
{
2142
2143
    size_t num_matches = 0;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2144

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

2147
    *list = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2148

2149
2150
2151
2152
    /* 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
2153

2154
2155
2156
2157
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2158

2159
2160
2161
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
	matches = cwd_tab_completion(buf, &num_matches, *place);
2162

2163
2164
2165
2166
2167
    if (num_matches <= 0)
	beep();
    else {
	size_t match, common_len = 0;
	char *mzero;
2168
2169
2170
	const char *lastslash = revstrstr(buf, "/", buf + *place);
	size_t lastslash_len = (lastslash == NULL) ? 0 :
		lastslash - buf + 1;
2171
2172
2173
2174
2175
2176
2177

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

2179
	    if (match < num_matches || matches[0][common_len] == '\0')
2180
		break;
2181

2182
2183
	    common_len++;
	}
2184

2185
2186
	mzero = charalloc(lastslash_len + common_len + 1);
	sprintf(mzero, "%.*s%.*s", lastslash_len, buf, common_len,
2187
		matches[0]);
2188

2189
	common_len += lastslash_len;
2190

2191
	assert(common_len >= *place);
2192

2193
2194
2195
2196
2197
	if (num_matches == 1 && is_dir(mzero)) {
	    mzero[common_len] = '/';
	    common_len++;
	    assert(common_len > *place);
	}
2198

2199
2200
2201
	if (num_matches > 1 && (common_len != *place ||
		*lastwastab == FALSE))
	    beep();
2202

2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
	/* 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;
2220

2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
	    /* 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;
2231
2232
		    break;
		}
2233
2234
		if (common_len > longest_name)
		    longest_name = common_len;
2235
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2236

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

2239
2240
2241
2242
	    /* 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);
2243

2244
2245
2246
2247
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2248

2249
2250
	    /* Disable el cursor. */
	    curs_set(0);
2251

2252
2253
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2254

2255
2256
		wmove(edit, editline, (longest_name + 2) *
			(match % columns));
2257

2258
2259
2260
2261
2262
		if (match % columns == 0 && editline == editwinrows - 1
			&& num_matches - match > columns) {
		    waddstr(edit, _("(more)"));
		    break;
		}
2263

2264
2265
2266
2267
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2268

2269
		if ((match + 1) % columns == 0)
2270
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2271
2272
	    }
	    wrefresh(edit);
2273
	    *list = TRUE;
2274
2275
2276
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2277
2278
    }

2279
2280
    free_charptrarray(matches, num_matches);

2281
    /* Only refresh the edit window if we don't have a list of filename
2282
     * matches on it. */
2283
    if (*list == FALSE)
2284
	edit_refresh();
2285
2286

    /* Enable el cursor. */
2287
    curs_set(1);
2288

2289
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2290
}
2291
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2292

2293
2294
/* Only print the last part of a path.  Isn't there a shell command for
 * this? */
2295
2296
const char *tail(const char *foo)
{
2297
    const char *tmp = strrchr(foo, '/');
2298

2299
2300
2301
    if (tmp == NULL)
	tmp = foo;
    else if (*tmp == '/')
2302
2303
2304
2305
2306
	tmp++;

    return tmp;
}

2307
#ifndef DISABLE_BROWSER
2308
/* Free our malloc()ed memory. */
2309
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2310
{
2311
2312
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2313
2314
2315
    free(array);
}

2316
2317
/* Strip one directory from the end of path. */
void striponedir(char *path)
Chris Allegretta's avatar
Chris Allegretta committed
2318
{
2319
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2320

2321
    assert(path != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2322

2323
    tmp = strrchr(path, '/');
2324
2325
    if (tmp != NULL)
 	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2326
2327
}

2328
2329
2330
2331
2332
2333
/* Return a list of files contained in the directory path.  *longest is
 * the maximum display length of a file, up to COLS - 1 (but at least
 * 7).  *numents is the number of files.  We assume path exists and is a
 * directory.  If neither is true, we return NULL. */
char **browser_init(const char *path, int *longest, size_t *numents, DIR
	*dir)
2334
{
2335
2336
2337
    const struct dirent *next;
    char **filelist;
    size_t i, path_len;
2338

2339
    assert(dir != NULL);
2340

2341
    *longest = 0;
2342

2343
    i = 0;
2344
2345

    while ((next = readdir(dir)) != NULL) {
2346
2347
2348
	size_t dlen;

	/* Don't show the . entry. */
2349
	if (strcmp(next->d_name, ".") == 0)
2350
	   continue;
2351
2352
2353
2354
2355
	i++;

	dlen = strlenpt(next->d_name);
	if (dlen > *longest)
	    *longest = dlen;
2356
    }
2357
2358

    *numents = i;
2359
2360
2361
    rewinddir(dir);
    *longest += 10;

2362
    filelist = (char **)nmalloc(*numents * sizeof(char *));
2363

2364
2365
    path_len = strlen(path);

2366
2367
2368
2369
    i = 0;

    while ((next = readdir(dir)) != NULL && i < *numents) {
	/* Don't show the "." entry. */
2370
	if (strcmp(next->d_name, ".") == 0)
2371
2372
	   continue;

2373
2374
	filelist[i] = charalloc(path_len + strlen(next->d_name) + 1);
	sprintf(filelist[i], "%s%s", path, next->d_name);
2375
2376
	i++;
    }
2377
2378
2379
2380
2381

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

    if (*longest > COLS - 1)
	*longest = COLS - 1;
2386
2387
    if (*longest < 7)
	*longest = 7;
2388
2389
2390
2391

    return filelist;
}

2392
2393
2394
/* Our browser function.  path is the path to start browsing from.
 * Assume path has already been tilde-expanded. */
char *do_browser(char *path, DIR *dir)
Chris Allegretta's avatar
Chris Allegretta committed
2395
{
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
    int kbinput, longest, selected, width;
    bool meta_key, func_key, old_constupdate = ISSET(CONSTUPDATE);
    size_t numents;
    char **filelist, *retval = NULL;

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

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

2412
    UNSET(CONSTUPDATE);
2413

2414
2415
2416
2417
2418
2419
2420
2421
  change_browser_directory:
	/* We go here after the user selects a new directory. */

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

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

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

2426
2427
2428
2429
    /* Get the list of files. */
    filelist = browser_init(path, &longest, &numents, dir);

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

2431
    /* Sort the list. */
Chris Allegretta's avatar
Chris Allegretta committed
2432
2433
2434
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
Chris Allegretta's avatar
Chris Allegretta committed
2435
2436

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2437
    do {
2438
	bool abort = FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2439
	int j, col = 0, editline = 0, fileline;
2440
2441
2442
2443
	int filecols = 0;
	    /* Used only if width == 0, to calculate the number of files
	     * per row below. */
	struct stat st;
2444
2445
	char *new_path;
	    /* Used by the Go To Directory prompt. */
2446
2447
2448
#ifndef DISABLE_MOUSE
	MEVENT mevent;
#endif
Rocco Corsi's avatar
   
Rocco Corsi committed
2449

2450
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2451

2452
2453
	/* Compute the line number we're on now, so that we don't divide
	 * by zero later. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2454
	fileline = selected;
2455
	if (width != 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2456
	    fileline /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2457
2458

	switch (kbinput) {
2459
#ifndef DISABLE_MOUSE
2460
2461
2462
	    case KEY_MOUSE:
		if (getmouse(&mevent) == ERR)
		    break;
2463

2464
2465
2466
2467
2468
2469
2470
2471
2472
		/* If we clicked in the edit window, we probably clicked
		 * on a file. */
		if (wenclose(edit, mevent.y, mevent.x)) {
		    int selectedbackup = selected;

		    mevent.y -= 2;

		    /* longest is the width of each column.  There are
		     * two spaces between each column. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2473
		    selected = (fileline / editwinrows) * editwinrows *
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
			width + mevent.y * width + mevent.x /
			(longest + 2);

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

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

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

2553
#ifndef DISABLE_OPERATINGDIR
2554
2555
2556
2557
2558
2559
		/* Note: the selected file can be outside the operating
		 * directory if it's ".." or if it's a symlink to a
		 * directory outside the operating directory. */
		if (check_operating_dir(filelist[selected], FALSE)) {
		    statusbar(
			_("Can't go outside of %s in restricted mode"),
2560
			operating_dir);
2561
2562
2563
		    beep();
		    break;
		}
2564
2565
#endif

2566
2567
2568
2569
2570
2571
		if (stat(filelist[selected], &st) == -1) {
		    statusbar(_("Error reading %s: %s"),
			filelist[selected], strerror(errno));
		    beep();
		    break;
		}
2572

2573
2574
2575
2576
2577
		if (!S_ISDIR(st.st_mode)) {
		    retval = mallocstrcpy(retval, filelist[selected]);
		    abort = TRUE;
		    break;
		}
2578

2579
2580
2581
2582
2583
2584
2585
		dir = opendir(filelist[selected]);
		if (dir == NULL) {
		    /* We can't open this dir for some reason.
		     * Complain. */
		    statusbar(_("Error reading %s: %s"),
			filelist[selected], strerror(errno));
		    break;
Chris Allegretta's avatar
Chris Allegretta committed
2586
		}
Chris Allegretta's avatar
Chris Allegretta committed
2587

2588
		path = mallocstrcpy(path, filelist[selected]);
2589

2590
2591
2592
2593
		/* Start over again with the new path value. */
		free_charptrarray(filelist, numents);
		goto change_browser_directory;

2594
2595
2596
2597
2598
	    /* Refresh the screen. */
	    case NANO_REFRESH_KEY:
		total_update();
		break;

2599
2600
2601
2602
2603
2604
2605
2606
	    /* Go to a specific directory. */
	    case NANO_GOTOLINE_KEY:
	    case NANO_GOTOLINE_FKEY:
	    case 'G': /* Pico compatibility. */
	    case 'g':
		curs_set(1);

		j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2607
#ifndef NANO_SMALL
2608
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2609
#endif
2610
			_("Go To Directory"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2611

2612
2613
		curs_set(0);
		bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2614

2615
2616
2617
2618
		if (j < 0) {
		    statusbar(_("Cancelled"));
		    break;
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2619

2620
2621
2622
2623
2624
2625
2626
		new_path = real_dir_from_tilde(answer);

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

2628
#ifndef DISABLE_OPERATINGDIR
2629
2630
2631
2632
2633
2634
2635
		if (check_operating_dir(new_path, FALSE)) {
		    statusbar(
			_("Can't go outside of %s in restricted mode"),
			operating_dir);
		    free(new_path);
		    break;
		}
2636
2637
#endif

2638
2639
2640
2641
2642
2643
2644
2645
2646
		dir = opendir(new_path);
		if (dir == NULL) {
		    /* We can't open this dir for some reason.
		     * Complain. */
		    statusbar(_("Error reading %s: %s"), answer,
			strerror(errno));
		    free(new_path);
		    break;
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2647

2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
		/* Start over again with the new path value. */
		free(path);
		path = new_path;
		free_charptrarray(filelist, numents);
		goto change_browser_directory;

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

Chris Allegretta's avatar
Chris Allegretta committed
2663
2664
2665
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2666
2667
	blank_edit();

2668
	if (width != 0)
2669
2670
	    j = width * editwinrows *
		((selected / width) / editwinrows);
Chris Allegretta's avatar
Chris Allegretta committed
2671
	else
2672
	    j = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2673
2674
2675

	wmove(edit, 0, 0);

2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
	{
	    int foo_len = mb_cur_max() * 7;
	    char *foo = charalloc(foo_len + 1);

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

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

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

		col += longest;
		filecols++;

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

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

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

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

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

	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2745
	}
2746

2747
	wrefresh(edit);
2748
2749
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
2750

Chris Allegretta's avatar
Chris Allegretta committed
2751
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2752
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2753
    edit_refresh();
2754
2755
2756
    curs_set(1);
    if (old_constupdate)
	SET(CONSTUPDATE);
Chris Allegretta's avatar
Chris Allegretta committed
2757

2758
    /* Clean up. */
Chris Allegretta's avatar
Chris Allegretta committed
2759
    free_charptrarray(filelist, numents);
2760
2761
    free(path);

Chris Allegretta's avatar
Chris Allegretta committed
2762
2763
    return retval;
}
2764

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2765
2766
2767
/* The file browser front end.  We check to see if inpath has a dir in
 * it.  If it does, we start do_browser() from there.  Otherwise, we
 * start do_browser() from the current directory. */
2768
char *do_browse_from(const char *inpath)
2769
2770
{
    struct stat st;
2771
    char *path;
2772
	/* This holds the tilde-expanded version of inpath. */
2773
    DIR *dir = NULL;
2774

2775
2776
2777
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2778

2779
2780
2781
2782
2783
2784
    /* Perhaps path is a directory.  If so, we'll pass it to
     * do_browser().  Or perhaps path is a directory / a file.  If so,
     * we'll try stripping off the last path element and passing it to
     * do_browser().  Or perhaps path doesn't have a directory portion
     * at all.  If so, we'll just pass the current directory to
     * do_browser(). */
2785
2786
    if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	striponedir(path);
2787
2788
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2789

2790
2791
	    path = charalloc(PATH_MAX + 1);
	    path = getcwd(path, PATH_MAX + 1);
2792
2793
2794

	    if (path != NULL)
		align(&path);
2795
	}
2796
    }
2797

2798
#ifndef DISABLE_OPERATINGDIR
2799
2800
2801
2802
2803
2804
2805
    /* If the resulting path isn't in the operating directory, use
     * the operating directory instead. */
    if (check_operating_dir(path, FALSE)) {
	if (path != NULL)
	    free(path);
	path = mallocstrcpy(NULL, operating_dir);
    }
2806
2807
#endif

2808
2809
2810
    if (path != NULL)
	dir = opendir(path);

2811
    if (dir == NULL) {
2812
	beep();
2813
2814
2815
2816
2817
	free(path);
	return NULL;
    }

    return do_browser(path, dir);
2818
}
Chris Allegretta's avatar
Chris Allegretta committed
2819
#endif /* !DISABLE_BROWSER */
2820

2821
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2822
2823
2824
/* 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)
2825
{
2826
    char *nanohist = NULL;
2827

2828
2829
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2830

2831
2832
2833
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2834
    }
2835
2836
2837
2838
2839
2840
    return nanohist;
}

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

2842
    /* assume do_rcfile() has reported missing home dir */
2843
2844
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2845

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

/* save histories to ~/.nano_history */
void save_history(void)
{
2901
    char *nanohist;
2902
2903

    /* don't save unchanged or empty histories */
2904
    if ((search_history.count == 0 && replace_history.count == 0) ||
2905
	!ISSET(HISTORY_CHANGED) || ISSET(VIEW_MODE))
2906
2907
	return;

2908
2909
2910
2911
    nanohist = histfilename();

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

2913
	if (hist == NULL)
2914
2915
	    rcfile_error(N_("Error writing %s: %s"), nanohist,
		strerror(errno));
2916
	else {
2917
2918
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2919
2920
2921
2922

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
2923
2924
		rcfile_error(N_("Error writing %s: %s"), nanohist,
			strerror(errno));
2925
2926
2927
2928
2929
	    fclose(hist);
	}
	free(nanohist);
    }
}
2930
#endif /* !NANO_SMALL && ENABLE_NANORC */