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
38
#include "proto.h"
#include "nano.h"
Chris Allegretta's avatar
Chris Allegretta committed
39

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

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

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

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

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

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

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

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

86
87
88
    if (*first_line_ins || fileage == NULL) {
	/* Special case: we're inserting with the cursor on the first
	 * line. */
Chris Allegretta's avatar
Chris Allegretta committed
89
90
91
	fileptr->prev = NULL;
	fileptr->next = fileage;
	fileptr->lineno = 1;
92
93
	if (*first_line_ins) {
	    *first_line_ins = FALSE;
94
	    /* If we're inserting into the first line of the file, then
95
96
	     * we want to make sure that our edit buffer stays on the
	     * first line and that fileage stays up to date. */
97
98
99
	    edittop = fileptr;
	} else
	    filebot = fileptr;
Chris Allegretta's avatar
Chris Allegretta committed
100
	fileage = fileptr;
101
102
    } else {
	assert(prev != NULL);
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
{
    int i = 0;
382
383
    char *buf;
    size_t namelen = strlen(name);
384

385
    buf = charalloc(namelen + num_of_digits(INT_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
396
397
398
	if (i == INT_MAX)
	    break;

	i++;
399
	sprintf(buf + namelen, ".%d", 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
    return buf;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

553
554
	/* If we're in multibuffer mode and the filename or command is
	 * blank, open a new buffer instead of canceling. */
555
	if (i == -1 || (i == -2
556
#ifdef ENABLE_MULTIBUFFER
557
		&& !ISSET(MULTIBUFFER)
558
#endif
559
		))
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
		free(answer);
		answer = tmp;
585
586
587
588

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

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

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

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

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

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

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

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

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

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

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

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

	    break;
	}
678
    }
Chris Allegretta's avatar
Chris Allegretta committed
679

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

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

    display_main_list();
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

901
    assert(open_files != NULL);
902

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    free(full_tempdir);
1168

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

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

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

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

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

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

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

1213
    assert(full_operating_dir != NULL);
1214
1215

    fullpath = get_full_path(currpath);
1216
1217

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1493
    /* If we're prepending, copy the file to a temp file. */
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
    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
1509
1510
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1511
	    unlink(tempname);
1512
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1513
	}
1514

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    retval = 1;

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

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

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

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

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

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

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

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

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

1723
    currshortcut = writefile_list;
1724

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    free(ans);
1889

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

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

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

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

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

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

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

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

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

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

1942
    return dirtmp;
1943
1944
}

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

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

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

1977
    free(dirptr);
1978

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

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

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

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

2009
    *num_matches = 0;
2010

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

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

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

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

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

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

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

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

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

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

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

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

#ifdef DEBUG
2092
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2093
#endif
2094
2095
2096
2097
2098
2099
	/* 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... */
2100
2101
2102

#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
	     * 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;
2114
	    }
2115
	    free(tmp2);
2116
2117
#endif

2118
2119
2120
2121
	    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
2122
2123
	}
    }
2124
2125
    closedir(dir);
    free(dirname);
2126
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2127

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

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

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

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

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

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

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

2156
2157
2158
2159
2160
    if (num_matches <= 0)
	beep();
    else {
	size_t match, common_len = 0;
	char *mzero;
2161
2162
2163
	const char *lastslash = revstrstr(buf, "/", buf + *place);
	size_t lastslash_len = (lastslash == NULL) ? 0 :
		lastslash - buf + 1;
2164
2165
2166
2167
2168
2169
2170

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

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

2175
2176
	    common_len++;
	}
2177

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

2182
	common_len += lastslash_len;
2183

2184
	assert(common_len >= *place);
2185

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

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

2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
	/* 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;
2213

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

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

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

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

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

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

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

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

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

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

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

2272
2273
    free_charptrarray(matches, num_matches);

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

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

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

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

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

    return tmp;
}

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

2309
2310
/* Strip one directory from the end of path. */
void striponedir(char *path)
Chris Allegretta's avatar
Chris Allegretta committed
2311
{
2312
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2313

2314
    assert(path != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2315

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

2321
2322
2323
2324
2325
2326
/* 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)
2327
{
2328
2329
2330
    const struct dirent *next;
    char **filelist;
    size_t i, path_len;
2331

2332
    assert(dir != NULL);
2333

2334
    *longest = 0;
2335

2336
    i = 0;
2337
2338

    while ((next = readdir(dir)) != NULL) {
2339
2340
2341
	size_t dlen;

	/* Don't show the . entry. */
2342
	if (strcmp(next->d_name, ".") == 0)
2343
	   continue;
2344
2345
2346
2347
2348
	i++;

	dlen = strlenpt(next->d_name);
	if (dlen > *longest)
	    *longest = dlen;
2349
    }
2350
2351

    *numents = i;
2352
2353
2354
    rewinddir(dir);
    *longest += 10;

2355
    filelist = (char **)nmalloc(*numents * sizeof(char *));
2356

2357
2358
    path_len = strlen(path);

2359
2360
2361
2362
    i = 0;

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

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

    /* 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;
2375
    closedir(dir);
2376
2377
2378

    if (*longest > COLS - 1)
	*longest = COLS - 1;
2379
2380
    if (*longest < 7)
	*longest = 7;
2381
2382
2383
2384

    return filelist;
}

2385
2386
2387
/* 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
2388
{
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
    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;
2403
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2404

2405
    UNSET(CONSTUPDATE);
2406

2407
2408
2409
2410
2411
2412
2413
2414
  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
2415

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

2419
2420
2421
2422
    /* Get the list of files. */
    filelist = browser_init(path, &longest, &numents, dir);

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

2424
    /* Sort the list. */
Chris Allegretta's avatar
Chris Allegretta committed
2425
2426
2427
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
Chris Allegretta's avatar
Chris Allegretta committed
2428
2429

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2430
    do {
2431
2432
2433
2434
2435
2436
	bool abort = FALSE;
	int j, col = 0, editline = 0, lineno;
	int filecols = 0;
	    /* Used only if width == 0, to calculate the number of files
	     * per row below. */
	struct stat st;
2437
2438
	char *new_path;
	    /* Used by the Go To Directory prompt. */
2439
2440
2441
#ifndef DISABLE_MOUSE
	MEVENT mevent;
#endif
Rocco Corsi's avatar
   
Rocco Corsi committed
2442

2443
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2444

2445
2446
	/* Compute the line number we're on now, so that we don't divide
	 * by zero later. */
2447
2448
2449
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2450
2451

	switch (kbinput) {
2452
#ifndef DISABLE_MOUSE
2453
2454
2455
	    case KEY_MOUSE:
		if (getmouse(&mevent) == ERR)
		    break;
2456

2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
		/* If we clicked in the edit window, we probably clicked
		 * on a file. */
		if (wenclose(edit, mevent.y, mevent.x)) {
		    int selectedbackup = selected;

		    mevent.y -= 2;

		    /* longest is the width of each column.  There are
		     * two spaces between each column. */
		    selected = (lineno / editwinrows) * editwinrows *
			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);
		}
2489

2490
		break;
2491
#endif
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
	    case NANO_PREVLINE_KEY:
		if (selected >= width)
		    selected -= width;
		break;
	    case NANO_BACK_KEY:
		if (selected > 0)
		    selected--;
		break;
	    case NANO_NEXTLINE_KEY:
		if (selected + width <= numents - 1)
		    selected += width;
		break;
	    case NANO_FORWARD_KEY:
		if (selected < numents - 1)
		    selected++;
		break;
	    case NANO_PREVPAGE_KEY:
	    case NANO_PREVPAGE_FKEY:
	    case '-': /* Pico compatibility. */
		if (selected >= (editwinrows + lineno % editwinrows) *
			width)
		    selected -= (editwinrows + lineno % editwinrows) *
			width;
		else
		    selected = 0;
		break;
	    case NANO_NEXTPAGE_KEY:
	    case NANO_NEXTPAGE_FKEY:
	    case ' ': /* Pico compatibility. */
		selected += (editwinrows - lineno % editwinrows) *
			width;
		if (selected >= numents)
		    selected = numents - 1;
		break;
	    case NANO_HELP_KEY:
	    case NANO_HELP_FKEY:
	    case '?': /* Pico compatibility. */
2529
#ifndef DISABLE_HELP
2530
2531
		do_help();
		curs_set(0);
2532
#else
2533
		nano_disabled_msg();
2534
#endif
Rocco Corsi's avatar
   
Rocco Corsi committed
2535
		break;
2536
2537
2538
2539
2540
2541
2542
2543
2544
	    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
2545

2546
#ifndef DISABLE_OPERATINGDIR
2547
2548
2549
2550
2551
2552
		/* 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"),
2553
			operating_dir);
2554
2555
2556
		    beep();
		    break;
		}
2557
2558
#endif

2559
2560
2561
2562
2563
2564
		if (stat(filelist[selected], &st) == -1) {
		    statusbar(_("Error reading %s: %s"),
			filelist[selected], strerror(errno));
		    beep();
		    break;
		}
2565

2566
2567
2568
2569
2570
		if (!S_ISDIR(st.st_mode)) {
		    retval = mallocstrcpy(retval, filelist[selected]);
		    abort = TRUE;
		    break;
		}
2571

2572
2573
2574
2575
2576
2577
2578
		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
2579
		}
Chris Allegretta's avatar
Chris Allegretta committed
2580

2581
		path = mallocstrcpy(path, filelist[selected]);
2582

2583
2584
2585
2586
		/* Start over again with the new path value. */
		free_charptrarray(filelist, numents);
		goto change_browser_directory;

2587
2588
2589
2590
2591
	    /* Refresh the screen. */
	    case NANO_REFRESH_KEY:
		total_update();
		break;

2592
2593
2594
2595
2596
2597
2598
2599
	    /* 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
2600
#ifndef NANO_SMALL
2601
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2602
#endif
2603
			_("Go To Directory"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2604

2605
2606
		curs_set(0);
		bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2607

2608
2609
2610
2611
		if (j < 0) {
		    statusbar(_("Cancelled"));
		    break;
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2612

2613
2614
2615
2616
2617
2618
2619
		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
2620

2621
#ifndef DISABLE_OPERATINGDIR
2622
2623
2624
2625
2626
2627
2628
		if (check_operating_dir(new_path, FALSE)) {
		    statusbar(
			_("Can't go outside of %s in restricted mode"),
			operating_dir);
		    free(new_path);
		    break;
		}
2629
2630
#endif

2631
2632
2633
2634
2635
2636
2637
2638
2639
		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
2640

2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
		/* 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_CANCEL_KEY:
	    case NANO_EXIT_KEY:
	    case NANO_EXIT_FKEY:
	    case 'E': /* Pico compatibility. */
	    case 'e':
		abort = TRUE;
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2655
	}
2656

Chris Allegretta's avatar
Chris Allegretta committed
2657
2658
2659
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2660
2661
	blank_edit();

2662
	if (width != 0)
2663
2664
	    j = width * editwinrows *
		((selected / width) / editwinrows);
Chris Allegretta's avatar
Chris Allegretta committed
2665
	else
2666
	    j = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2667
2668
2669

	wmove(edit, 0, 0);

2670
2671
2672
2673
2674
2675
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
	{
	    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
2736
	    }
2737
2738

	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2739
	}
2740

2741
	wrefresh(edit);
2742
2743
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
2744

Chris Allegretta's avatar
Chris Allegretta committed
2745
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2746
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2747
    edit_refresh();
2748
2749
2750
    curs_set(1);
    if (old_constupdate)
	SET(CONSTUPDATE);
Chris Allegretta's avatar
Chris Allegretta committed
2751

2752
    /* Clean up. */
Chris Allegretta's avatar
Chris Allegretta committed
2753
    free_charptrarray(filelist, numents);
2754
2755
    free(path);

Chris Allegretta's avatar
Chris Allegretta committed
2756
2757
    return retval;
}
2758

2759
2760
/* Browser front end, checks to see if inpath has a dir in it and, if
   so, starts do_browser from there, else from the current dir */
2761
char *do_browse_from(const char *inpath)
2762
2763
{
    struct stat st;
2764
    char *path;
2765
2766
	/* This holds the tilde-expanded version of inpath. */
    DIR *dir;
2767

2768
2769
2770
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2771

2772
2773
2774
2775
2776
2777
    /* 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(). */
2778
2779
    if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	striponedir(path);
2780
2781
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2782
2783
2784
2785
2786
2787
2788
2789
2790
#if PATH_MAX != -1
	    path = charalloc(PATH_MAX + 1);
#else
	    path = NULL;
#endif
	    path = getcwd(path, PATH_MAX + 1);
#if PATH_MAX != -1
	    align(&path);
#endif
2791
	}
2792
    }
2793

2794
#ifndef DISABLE_OPERATINGDIR
2795
2796
2797
2798
2799
2800
2801
    /* 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);
    }
2802
2803
#endif

2804
2805
    dir = opendir(path);
    if (dir == NULL) {
2806
	beep();
2807
2808
2809
2810
2811
	free(path);
	return NULL;
    }

    return do_browser(path, dir);
2812
}
Chris Allegretta's avatar
Chris Allegretta committed
2813
#endif /* !DISABLE_BROWSER */
2814

2815
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2816
2817
2818
/* 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)
2819
{
2820
    char *nanohist = NULL;
2821

2822
2823
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2824

2825
2826
2827
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2828
    }
2829
2830
2831
2832
2833
2834
    return nanohist;
}

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

2836
    /* assume do_rcfile() has reported missing home dir */
2837
2838
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2839

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

/* save histories to ~/.nano_history */
void save_history(void)
{
2895
    char *nanohist;
2896
2897

    /* don't save unchanged or empty histories */
2898
    if ((search_history.count == 0 && replace_history.count == 0) ||
2899
	!ISSET(HISTORY_CHANGED) || ISSET(VIEW_MODE))
2900
2901
	return;

2902
2903
2904
2905
    nanohist = histfilename();

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

2907
	if (hist == NULL)
2908
2909
	    rcfile_error(N_("Error writing %s: %s"), nanohist,
		strerror(errno));
2910
	else {
2911
2912
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2913
2914
2915
2916

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
2917
2918
		rcfile_error(N_("Error writing %s: %s"), nanohist,
			strerror(errno));
2919
2920
2921
2922
2923
	    fclose(hist);
	}
	free(nanohist);
    }
}
2924
#endif /* !NANO_SMALL && ENABLE_NANORC */