files.c 79 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   files.c                                                              *
 *                                                                        *
5
 *   Copyright (C) 1999-2005 Chris Allegretta                             *
Chris Allegretta's avatar
Chris Allegretta committed
6
7
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
8
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
9
10
11
12
13
14
15
16
17
18
19
20
21
 *   any later version.                                                   *
 *                                                                        *
 *   This program is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of       *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
 *   GNU General Public License for more details.                         *
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            *
 *                                                                        *
 **************************************************************************/

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

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
234
	    /* Now we allocate a bigger buffer 128 characters at a time.
235
236
237
	     * 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
238
239
	    if (i >= bufx - 1) {
		bufx += 128;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
240
		buf = charealloc(buf, bufx);
Chris Allegretta's avatar
Chris Allegretta committed
241
	    }
242
	    buf[i] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
243
	    buf[i + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
244
245
	    i++;
	}
246
247
248
	totsize++;
    }

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

255
#ifndef NANO_SMALL
256
257
258
    /* 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') {
259
260
261
	buf[0] = input;
	buf[1] = '\0';
	len = 1;
262
263
    }
#endif
264

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

277
278
279
280
281
282
283
284
285
	/* Read in the last line properly. */
	fileptr = read_line(buf, fileptr, &first_line_ins, len);
	num_lines++;
	totsize++;
    }
    free(buf);

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

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

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

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

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

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

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

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

383
    buf = charalloc(namelen + num_of_digits(INT_MAX) + 7);
384
    strcpy(buf, name);
385
386
    strcpy(buf + namelen, ".save");
    namelen += 5;
387

388
    while (TRUE) {
389
	struct stat fs;
390
391

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

	i++;
397
	sprintf(buf + namelen, ".%d", i);
398
399
    }

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

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

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

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

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

457
458
459
460
461
    rc = open_file(name, new_buffer, &f);

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

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

    if (rc == 0) {
474
475
	file_format fmt_save = fmt;

476
	read_file(f, filename);
477
478
479
480
481
482

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

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

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

511
#ifndef DISABLE_WRAPPING
512
    wrap_reset();
513
#endif
514

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

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

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

564
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
565

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

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

579
580
		if (tmp == NULL)
		    continue;
581
582
		free(answer);
		answer = tmp;
583
584
585
586

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    display_main_list();
}

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

/* Splice a node into an existing openfilestruct. */
void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
709
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
710
{
711
    assert(newnode != NULL && begin != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
712
713
714
715
716
717
718
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

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

/* Delete a node from the openfilestruct. */
void delete_opennode(openfilestruct *fileptr)
{
731
732
733
734
    assert(fileptr != NULL && fileptr->filename != NULL && fileptr->fileage != NULL);
    free(fileptr->filename);
    free_filestruct(fileptr->fileage);
    free(fileptr);
Chris Allegretta's avatar
Chris Allegretta committed
735
736
}

737
738
739
#ifdef DEBUG
/* Deallocate all memory associated with this and later files, including
 * the lines of text. */
Chris Allegretta's avatar
Chris Allegretta committed
740
741
void free_openfilestruct(openfilestruct *src)
{
742
743
744
745
    assert(src != NULL);
    while (src != src->next) {
	src = src->next;
	delete_opennode(src->prev);
Chris Allegretta's avatar
Chris Allegretta committed
746
    }
747
    delete_opennode(src);
Chris Allegretta's avatar
Chris Allegretta committed
748
}
749
#endif
Chris Allegretta's avatar
Chris Allegretta committed
750

751
/* Add/update an entry to the open_files openfilestruct.  If update is
752
 * FALSE, a new entry is created; otherwise, the current entry is
753
 * updated. */
754
void add_open_file(bool update)
755
{
756
    if (open_files == NULL && update)
757
	return;
758

759
760
761
762
763
764
765
766
767
    /* 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);
768
769
770
	open_files = open_files->next;
    }

771
    /* Save the current filename. */
772
    open_files->filename = mallocstrcpy(open_files->filename, filename);
773

774
#ifndef NANO_SMALL
775
    /* Save the current file's stat. */
776
777
778
    open_files->originalfilestat = originalfilestat;
#endif

779
780
781
782
783
784
785
786
787
    /* 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;
788

789
790
    /* Save the current cursor position. */
    open_files->current_x = current_x;
791

792
793
    /* Save the current place we want. */
    open_files->placewewant = placewewant;
794

795
796
    /* Save the current total number of lines. */
    open_files->totlines = totlines;
797

798
799
    /* Save the current total size. */
    open_files->totsize = totsize;
800

801
802
    /* Start with no flags saved. */
    open_files->flags = 0;
803

804
805
806
    /* Save the current modification status. */
    if (ISSET(MODIFIED))
	open_files->flags |= MODIFIED;
807

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
808
#ifndef NANO_SMALL
809
810
811
812
813
    /* 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
814
815
    }

816
817
818
    /* Save the current file format. */
    open_files->fmt = fmt;
#endif
819
820

#ifdef DEBUG
821
    fprintf(stderr, "filename is %s\n", open_files->filename);
822
823
824
#endif
}

825
/* Read the current entry in the open_files structure and set up the
826
 * currently open file buffer using that entry's information. */
827
void load_open_file(void)
828
{
829
    assert(open_files != NULL);
830

831
    /* Restore the current filename. */
832
    filename = mallocstrcpy(filename, open_files->filename);
833

834
#ifndef NANO_SMALL
835
    /* Restore the current file's stat. */
836
837
    originalfilestat = open_files->originalfilestat;
#endif
838
839

    /* Restore the current file buffer. */
840
    fileage = open_files->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
841
    filebot = open_files->filebot;
842

843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
    /* 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
863
864
865
866
867
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

#ifndef NANO_SMALL
868
869
870
871
    /* 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
872
873
874
	SET(MARK_ISSET);
    } else
	UNSET(MARK_ISSET);
875

876
877
    /* Restore the current file format. */
    fmt = open_files->fmt;
Chris Allegretta's avatar
Chris Allegretta committed
878
#endif
879

Chris Allegretta's avatar
Chris Allegretta committed
880
881
882
#ifdef ENABLE_COLOR
    update_color();
#endif
883
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
884

885
    /* Update the titlebar. */
886
887
888
889
    clearok(topwin, FALSE);
    titlebar(NULL);
}

890
/* Open either the next or previous file buffer. */
891
void open_prevnext_file(bool next)
892
{
893
    add_open_file(TRUE);
894

895
    assert(open_files != NULL);
896

897
898
    /* If only one file buffer is open, indicate it on the statusbar and
     * get out. */
899
    if (open_files == open_files->next) {
900
	statusbar(_("No more open file buffers"));
901
	return;
902
903
    }

904
905
906
    /* Switch to the next or previous file, depending on the value of
     * next. */
    open_files = next ? open_files->next : open_files->prev;
907
908

#ifdef DEBUG
909
    fprintf(stderr, "filename is %s\n", open_files->filename);
910
911
#endif

912
    /* Load the file we switched to. */
913
914
    load_open_file();

915
    /* And indicate the switch on the statusbar. */
916
    statusbar(_("Switched to %s"),
917
      ((open_files->filename[0] == '\0') ? _("New Buffer") :
918
	open_files->filename));
919

920
921
922
923
924
#ifdef DEBUG
    dump_buffer(current);
#endif
}

925
926
/* Open the previous entry in the open_files structure.  This function
 * is used by the shortcut list. */
927
void open_prevfile_void(void)
928
{
929
    open_prevnext_file(FALSE);
930
931
}

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

939
940
/* 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
941
 * there are no more open file buffers. */
942
bool close_open_file(void)
943
{
944
    assert(open_files != NULL);
945

946
947
    /* If only one file is open, get out. */
    if (open_files == open_files->next)
948
	return FALSE;
949

950
951
    /* Open the next file. */
    open_nextfile_void();
Chris Allegretta's avatar
Chris Allegretta committed
952

953
954
    /* Close the file we had open before. */
    unlink_opennode(open_files->prev);
955

956
    /* Reinitialize the shortcut list. */
957
    shortcut_init(FALSE);
958
    display_main_list();
959

960
    return TRUE;
961
}
962
#endif /* ENABLE_MULTIBUFFER */
963

964
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
965
/*
966
967
968
969
970
971
 * When passed "[relative path]" or "[relative path][filename]" in
 * origpath, return "[full path]" or "[full path][filename]" on success,
 * or NULL on error.  This is still done if the file doesn't exist but
 * the relative path does (since the file could exist in memory but not
 * yet on disk); it is not done if the relative path doesn't exist (since
 * the first call to chdir() will fail then).
972
 */
973
char *get_full_path(const char *origpath)
974
{
975
976
977
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
978
    char *expanded_origpath;
979

980
    /* first, get the current directory, and tack a slash onto the end of
981
       it, unless it turns out to be "/", in which case leave it alone */
982
983
984

    d_here = getcwd(NULL, PATH_MAX + 1);

985
    if (d_here != NULL) {
986
987

	align(&d_here);
988
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
989
	    d_here = charealloc(d_here, strlen(d_here) + 2);
990
991
	    strcat(d_here, "/");
	}
992
993
994
995
996

	/* stat origpath; if stat() fails, assume that origpath refers to
	   a new file that hasn't been saved to disk yet (i. e. set
	   path_only to 0); if stat() succeeds, set path_only to 0 if
	   origpath doesn't refer to a directory, or to 1 if it does */
997
	path_only = !stat(origpath, &fileinfo) && S_ISDIR(fileinfo.st_mode);
998

999
	expanded_origpath = real_dir_from_tilde(origpath);
1000
	/* save the value of origpath in both d_there and d_there_file */
1001
1002
1003
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1004

1005
1006
1007
1008
1009
1010
	/* if we have a path but no filename, tack slashes onto the ends
	   of both d_there and d_there_file, if they don't end in slashes
	   already */
	if (path_only) {
	    tmp = d_there[strlen(d_there) - 1];
	    if (tmp != '/') {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1011
		d_there = charealloc(d_there, strlen(d_there) + 2);
1012
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1013
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1014
1015
1016
1017
		strcat(d_there_file, "/");
	    }
	}

1018
1019
1020
1021
1022
	/* search for the last slash in d_there */
	last_slash = strrchr(d_there, '/');

	/* if we didn't find one, copy d_here into d_there; all data is
	   then set up */
1023
	if (last_slash == NULL)
1024
	    d_there = mallocstrcpy(d_there, d_here);
1025
	else {
1026
1027
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1028
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1029
	    null_at(&d_there, last_slash_index + 1);
1030
1031
1032

	    /* and remove all non-file elements from d_there_file (i. e.
	       everything before and including the last slash); if we
1033
	       have a path but no filename, don't do anything */
1034
1035
1036
1037
1038
1039
	    if (!path_only) {
		last_slash = strrchr(d_there_file, '/');
		last_slash++;
		strcpy(d_there_file, last_slash);
		align(&d_there_file);
	    }
1040
1041
1042

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1043
1044
1045
		/* get the full pathname, and save it back in d_there,
		   tacking a slash on the end if we have a path but no
		   filename; if the saving fails, get out */
1046
1047
1048
1049
1050
1051

		free(d_there);

		d_there = getcwd(NULL, PATH_MAX + 1);

		align(&d_there);
1052
		if (d_there != NULL) {
1053
1054
1055

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
1056
		    if (strcmp(d_there, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1057
			d_there = charealloc(d_there, strlen(d_there) + 2);
1058
1059
			strcat(d_there, "/");
		    }
1060
1061
1062
		}
		else
		    return NULL;
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
	    }

	    /* finally, go back to where we were before, d_here (no error
	       checking is done on this chdir(), because we can do
	       nothing if it fails) */
	    chdir(d_here);
	}
	
	/* all data is set up; fill in newpath */

1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
	/* if we have a path and a filename, newpath = d_there +
	   d_there_file; otherwise, newpath = d_there */
	if (!path_only) {
	    newpath = charalloc(strlen(d_there) + strlen(d_there_file) + 1);
	    strcpy(newpath, d_there);
	    strcat(newpath, d_there_file);
	}
	else {
	    newpath = charalloc(strlen(d_there) + 1);
	    strcpy(newpath, d_there);
	}
1084
1085
1086
1087
1088
1089
1090
1091
1092

	/* finally, clean up */
	free(d_there_file);
	free(d_there);
	free(d_here);
    }

    return newpath;
}
1093
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1094
1095
1096

#ifndef DISABLE_SPELLER
/*
1097
1098
1099
 * This function accepts a path and returns the full path (via
 * get_full_path()).  On error, if the path doesn't reference a
 * directory, or if the directory isn't writable, it returns NULL.
1100
 */
1101
char *check_writable_directory(const char *path)
1102
{
1103
    char *full_path = get_full_path(path);
1104
    int writable;
1105
1106
    struct stat fileinfo;

1107
    /* if get_full_path() failed, return NULL */
1108
    if (full_path == NULL)
1109
	return NULL;
1110
1111
1112

    /* otherwise, stat() the full path to see if it's writable by the
       user; set writable to 1 if it is, or 0 if it isn't */
1113
    writable = !stat(full_path, &fileinfo) && (fileinfo.st_mode & S_IWUSR);
1114
1115

    /* if the full path doesn't end in a slash (meaning get_full_path()
1116
1117
1118
1119
       found that it isn't a directory) or isn't writable, free full_path
       and return NULL */
    if (full_path[strlen(full_path) - 1] != '/' || writable == 0) {
	free(full_path);
1120
	return NULL;
1121
    }
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131

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

/*
 * This function accepts a directory name and filename prefix the same
 * way that tempnam() does, determines the location for its temporary
 * file the same way that tempnam() does, safely creates the temporary
 * file there via mkstemp(), and returns the name of the temporary file
1132
1133
1134
1135
1136
1137
1138
 * the same way that tempnam() does.  It does not reference the value of
 * TMP_MAX because the total number of random filenames that it can
 * generate using one prefix is equal to 256**6, which is a sufficiently
 * large number to handle most cases.  Since the behavior after tempnam()
 * generates TMP_MAX random filenames is implementation-defined, my
 * implementation is to go on generating random filenames regardless of
 * it.
1139
 */
1140
1141
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1142
1143
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1144
    int filedesc;
1145

1146
1147
      /* 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,
1148
         leave full_tempdir set to NULL */
1149
    TMPDIR_env = getenv("TMPDIR");
1150
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1151
	full_tempdir = check_writable_directory(TMPDIR_env);
1152

1153
1154
1155
    /* if $TMPDIR is blank or isn't set, or isn't a writable
       directory, and dirname isn't NULL, try it; otherwise, leave
       full_tempdir set to NULL */
1156
    if (full_tempdir == NULL && dirname != NULL)
1157
	full_tempdir = check_writable_directory(dirname);
1158
1159
1160

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1161
    if (full_tempdir == NULL)
1162
	full_tempdir = check_writable_directory(P_tmpdir);
1163
1164

    /* if P_tmpdir didn't work, use /tmp instead */
1165
    if (full_tempdir == NULL) {
1166
1167
1168
1169
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1170
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1171
1172

    /* like tempnam(), use only the first 5 characters of the prefix */
1173
1174
1175
1176
1177
1178
1179
1180
    strncat(full_tempdir, filename_prefix, 5);
    strcat(full_tempdir, "XXXXXX");
    filedesc = mkstemp(full_tempdir);

    /* if mkstemp succeeded, close the resulting file; afterwards, it'll be
       0 bytes long, so delete it; finally, return the filename (all that's
       left of it) */
    if (filedesc != -1) {
1181
	close(filedesc);
1182
1183
	unlink(full_tempdir);
	return full_tempdir;
1184
    }
1185
1186
1187

    free(full_tempdir);
    return NULL;
1188
1189
}
#endif /* !DISABLE_SPELLER */
1190
1191

#ifndef DISABLE_OPERATINGDIR
1192
1193
1194
1195
1196
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1197
    if (operating_dir == NULL)
1198
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1199

1200
1201
1202
    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
1203
     * inaccessible, unset operating_dir. */
1204
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1205
1206
1207
1208
1209
1210
1211
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1212
/* Check to see if we're inside the operating directory.  Return 0 if we
1213
1214
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1215
 * completion will work. */
1216
int check_operating_dir(const char *currpath, bool allow_tabcomp)
1217
{
1218
1219
1220
1221
    /* 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. */
1222

1223
1224
1225
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1226

1227
    /* If no operating directory is set, don't bother doing anything. */
1228
    if (operating_dir == NULL)
1229
	return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1230

1231
    assert(full_operating_dir != NULL);
1232
1233

    fullpath = get_full_path(currpath);
1234
1235
1236
1237
1238
1239
1240
1241

    /* fullpath == NULL means some directory in the path doesn't exist
     * or is unreadable.  If allow_tabcomp is zero, then currpath is
     * what the user typed somewhere.  We don't want to report a
     * non-existent directory as being outside the operating directory,
     * so we return 0.  If allow_tabcomp is nonzero, then currpath
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1242
    if (fullpath == NULL)
1243
	return allow_tabcomp;
1244
1245
1246
1247
1248

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

1249
1250
1251
1252
1253
    /* 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. */
1254
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1255
1256
	retval = 1;
    free(fullpath);	
1257
1258

    /* Otherwise, we're still inside it. */
1259
    return retval;
1260
}
1261
1262
#endif

1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
#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

1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
/* 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;
}

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

1360
    assert(name != NULL);
1361
    if (name[0] == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1362
	return -1;
1363
1364
    if (!tmp)
	titlebar(NULL);
1365

1366
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1367

1368
#ifndef DISABLE_OPERATINGDIR
1369
    /* If we're writing a temporary file, we're probably going outside
1370
     * the operating directory, so skip the operating directory test. */
1371
    if (!tmp && check_operating_dir(realname, FALSE) != 0) {
1372
1373
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1374
1375
1376
    }
#endif

1377
    anyexists = (lstat(realname, &lst) != -1);
1378

1379
1380
1381
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
1382

1383
1384
1385
    /* 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)) {
1386
1387
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1388
1389
1390
	goto cleanup_and_exit;
    }

1391
    /* Save the state of file at the end of the symlink (if there is
1392
     * one). */
1393
    realexists = (stat(realname, &st) != -1);
1394

1395
1396
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1397
1398
1399
1400
     * 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. */
1401
    if (ISSET(BACKUP_FILE) && !tmp && realexists &&
1402
1403
1404
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1405
	FILE *backup_file;
1406
	char *backupname;
1407
	struct utimbuf filetime;
1408
	int copy_status;
1409

1410
	/* Save the original file's access and modification times. */
1411
1412
1413
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1414
	/* Open the original file to copy to the backup. */
1415
	f = fopen(realname, "rb");
1416
	if (f == NULL) {
1417
	    statusbar(_("Error reading %s: %s"), realname,
1418
		strerror(errno));
1419
	    goto cleanup_and_exit;
1420
1421
	}

1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
	/* 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);
	}
1454

1455
1456
1457
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1458
	backup_file = fopen(backupname, "wb");
1459
1460
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1461
1462
	    statusbar(_("Error writing %s: %s"), backupname,
		strerror(errno));
1463
	    free(backupname);
1464
1465
1466
1467
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1468
1469
1470
	}

#ifdef DEBUG
1471
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1472
1473
#endif

1474
1475
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1476

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

1494
1495
1496
1497
1498
1499
    /* 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));
1500
	goto cleanup_and_exit;
1501
    }
1502

1503
1504
    original_umask = umask(0);
    umask(original_umask);
1505

1506
    /* If we create a temp file, we don't let anyone else access it.  We
1507
     * create a temp file if tmp is TRUE or if we're prepending. */
1508
1509
1510
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

1511
    /* If we're prepending, copy the file to a temp file. */
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
    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
1527
1528
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1529
	    unlink(tempname);
1530
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1531
	}
1532

1533
1534
1535
1536
1537
1538
1539
	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
1540
1541
	    statusbar(_("Error reading %s: %s"), realname,
		strerror(errno));
1542
1543
1544
1545
1546
1547
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

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

1555
1556
    /* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This is
     * now copied from joe, because wiggy says so *shrug*. */
1557
1558
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
1559
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1560

1561
    /* Set the umask back to the user's original value. */
1562
1563
1564
1565
1566
    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));
1567
1568
1569
	/* tempname has been set only if we're prepending. */
	if (tempname != NULL)
	    unlink(tempname);
1570
1571
	goto cleanup_and_exit;
    }
1572

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

1580
    /* There might not be a magicline.  There won't be when writing out
1581
1582
1583
1584
1585
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1586

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

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

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

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

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

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

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

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

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

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

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

    retval = 1;

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

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

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

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

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

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

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

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

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

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

1741
    currshortcut = writefile_list;
1742

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

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

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

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

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

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

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

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

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

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

1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
		currshortcut = writefile_list;

		if (tmp == NULL)
		    continue;
		free(answer);
		answer = tmp;

		/* We have a file now.  Get out of the statusbar prompt
		 * cleanly. */
		statusq_abort();
	    } else
1826
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1827
#ifndef NANO_SMALL
1828
	    if (i == TOGGLE_DOS_KEY) {
1829
		fmt = (fmt == DOS_FILE) ? NIX_FILE : DOS_FILE;
1830
1831
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1832
		fmt = (fmt == MAC_FILE) ? NIX_FILE : MAC_FILE;
1833
1834
1835
1836
1837
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1838
#endif /* !NANO_SMALL */
1839
1840
1841
1842
1843
1844
1845
	    if (i == NANO_PREPEND_KEY) {
		append = (append == 2) ? 0 : 2;
		continue;
	    } else if (i == NANO_APPEND_KEY) {
		append = (append == 1) ? 0 : 1;
		continue;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1846

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

#ifdef NANO_EXTRA
1852
1853
1854
1855
1856
1857
1858
	    if (exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0 && !did_cred) {
		do_credits();
		did_cred = TRUE;
		retval = -1;
		break;
	    }
1859
#endif
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
	    if (append == 0 && strcmp(answer, filename) != 0) {
		struct stat st;

		if (!stat(answer, &st)) {
		    i = do_yesno(FALSE, _("File exists, OVERWRITE ? "));
		    if (i == 0 || i == -1)
			continue;
		/* If we're using restricted mode, we aren't allowed to
		 * change the name of a file once it has one because
		 * that would allow reading from or writing to files not
		 * specified on the command line.  In this case, don't
		 * bother showing the "Different Name" prompt. */
		} else if (!ISSET(RESTRICTED) && filename[0] != '\0'
1873
#ifndef NANO_SMALL
1874
			&& (exiting || !ISSET(MARK_ISSET))
1875
#endif
1876
			) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1877
1878
		    i = do_yesno(FALSE,
			_("Save file under DIFFERENT NAME ? "));
1879
1880
1881
		    if (i == 0 || i == -1)
			continue;
		}
1882
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1883

1884
#ifndef NANO_SMALL
1885
1886
1887
1888
1889
1890
1891
	    /* Here's where we allow the selected text to be written to
	     * a separate file.  If we're using restricted mode, this is
	     * disabled since it allows reading from or writing to files
	     * not specified on the command line. */
	    if (!ISSET(RESTRICTED) && !exiting && ISSET(MARK_ISSET))
		retval = write_marked(answer, FALSE, append);
	    else
1892
#endif /* !NANO_SMALL */
1893
		retval = write_file(answer, FALSE, append, FALSE);
1894

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

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

    free(ans);
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1908
1909
}

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

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

1922
    if (buf[0] == '~') {
1923
1924
1925
1926
1927
1928
1929
	size_t i;
	const struct passwd *userdata;

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

1930
1931
	/* Determine home directory using getpwuid() or getpwent(),
	   don't rely on $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
1932
1933
1934
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
1935
1936
1937
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1938
		strncmp(userdata->pw_name, buf + 1, i - 1) != 0);
Chris Allegretta's avatar
Chris Allegretta committed
1939
1940
	}
	endpwent();
1941

1942
	if (userdata != NULL) {	/* User found */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1943
1944
	    dirtmp = charalloc(strlen(userdata->pw_dir) +
		strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1945
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1946
	}
1947
    }
1948

1949
1950
1951
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1952
    return dirtmp;
1953
1954
}

Chris Allegretta's avatar
Chris Allegretta committed
1955
#ifndef DISABLE_TABCOMP
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1956
1957
1958
/* Tack a slash onto the string we're completing if it's a directory.
 * We assume there is room for one more character on the end of buf.
 * The return value says whether buf is a directory. */
1959
int append_slash_if_dir(char *buf, bool *lastwastab, int *place)
1960
{
1961
    char *dirptr = real_dir_from_tilde(buf);
1962
    struct stat fileinfo;
1963
    int ret = 0;
1964

1965
    assert(dirptr != buf);
1966

1967
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
1968
	strncat(buf, "/", 1);
1969
	(*place)++;
1970
	/* now we start over again with # of tabs so far */
1971
	*lastwastab = FALSE;
1972
	ret = 1;
1973
1974
    }

1975
    free(dirptr);
1976
    return ret;
1977
}
Chris Allegretta's avatar
Chris Allegretta committed
1978
1979

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

char **username_tab_completion(char *buf, int *num_matches)
{
2000
    char **matches = (char **)NULL;
2001
    char *matchline = NULL;
2002
    struct passwd *userdata;
2003

2004
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2005
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2006

2007
    strcat(buf, "*");
2008

2009
    while ((userdata = getpwent()) != NULL) {
2010

2011
	if (check_wildcard_match(userdata->pw_name, &buf[1])) {
2012
2013
2014
2015

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2016

2017
2018
2019
2020
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2021
	    if (operating_dir != NULL) {
2022
		if (check_operating_dir(userdata->pw_dir, TRUE) != 0)
2023
2024
2025
2026
		    continue;
	    }
#endif

2027
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2028
2029
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
2030
	    ++(*num_matches);
2031

2032
2033
2034
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2035
	}
2036
2037
    }
    endpwent();
2038

2039
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2040
2041
2042
2043
2044
2045
2046
}

/* This was originally called exe_n_cwd_tab_completion, but we're not
   worried about executables, only filenames :> */

char **cwd_tab_completion(char *buf, int *num_matches)
{
Chris Allegretta's avatar
Chris Allegretta committed
2047
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2048
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2049
2050
2051
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2052
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2053
2054
2055
2056

    /* Stick a wildcard onto the buf, for later use */
    strcat(buf, "*");

2057
    /* Okie, if there's a / in the buffer, strip out the directory part */
2058
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2059
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2060
2061
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2062
	    tmp--;
2063

Chris Allegretta's avatar
Chris Allegretta committed
2064
2065
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2066
2067
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2068

Chris Allegretta's avatar
Chris Allegretta committed
2069
    } else {
2070

Chris Allegretta's avatar
Chris Allegretta committed
2071
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2072
2073
2074
2075
2076
2077
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2078
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2079
2080
2081
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2082

Chris Allegretta's avatar
Chris Allegretta committed
2083
2084
2085
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2086
2087

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2088
    fprintf(stderr, "\nDir = %s\n", dirname);
2089
2090
2091
2092
2093
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2094
    dir = opendir(dirname);
2095
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2096
2097
2098
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2099
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2100
2101
2102
2103
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2104
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2105
2106
#endif
	/* See if this matches */
2107
	if (check_wildcard_match(next->d_name, tmp)) {
2108
2109
2110
2111

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2112
2113
2114
2115
2116
2117
2118
2119

#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               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 */

2120
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2121
2122
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2123
2124
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2125
		if (check_operating_dir(tmp2, TRUE) != 0) {
2126
2127
2128
2129
2130
2131
2132
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2133
	    tmp2 = NULL;
2134
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2135
2136
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2137
	    ++*num_matches;
2138
2139
2140
2141

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2142
2143
	}
    }
2144
2145
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2146

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

Chris Allegretta's avatar
Chris Allegretta committed
2150
2151
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
2152
2153
char *input_tab(char *buf, int place, bool *lastwastab, int *newplace,
	bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2154
2155
{
    /* Do TAB completion */
2156
    static int num_matches = 0, match_matches = 0;
2157
    static char **matches = (char **)NULL;
2158
    int pos = place, i = 0, col = 0, editline = 0;
2159
    int longestname = 0, is_dir = 0;
2160
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2161

2162
    *list = FALSE;
2163

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2164
    if (*lastwastab == FALSE) {
2165
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2166

2167
	*lastwastab = TRUE;
2168

Chris Allegretta's avatar
Chris Allegretta committed
2169
2170
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2171
2172
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2173

2174
2175
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2176
2177

	/* skip any leading white space */
2178
	while (*tmp && is_blank_char(*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2179
2180
2181
2182
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2183
2184
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2185
	    free(matches);
2186
	    matches = (char **)NULL;
2187
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2188
2189
2190
2191
2192
	}

	/* If the word starts with `~' and there is no slash in the word, 
	 * then try completing this word as a username. */

Chris Allegretta's avatar
Chris Allegretta committed
2193
2194
2195
2196
2197
	/* If the original string begins with a tilde, and the part
	   we're trying to tab-complete doesn't contain a slash, copy
	   the part we're tab-completing into buf, so tab completion
	   will result in buf's containing only the tab-completed
	   username. */
2198
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2199
	    buf = mallocstrcpy(buf, tmp);
2200
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2201
	}
2202
2203
2204
2205
2206
2207
	/* If we're in the middle of the original line, copy the string
	   only up to the cursor position into buf, so tab completion
	   will result in buf's containing only the tab-completed
	   path/filename. */
	else if (strlen(buf) > strlen(tmp))
	    buf = mallocstrcpy(buf, tmp);
Chris Allegretta's avatar
Chris Allegretta committed
2208
2209
2210

	/* Try to match everything in the current working directory that
	 * matches.  */
2211
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2212
2213
2214
	    matches = cwd_tab_completion(tmp, &num_matches);

	/* Don't leak memory */
2215
	free(matchbuf);
Chris Allegretta's avatar
Chris Allegretta committed
2216

2217
2218
2219
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2220
	/* Did we find exactly one match? */
2221
	switch (num_matches) {
2222
	case 0:
2223
	    blank_edit();
2224
	    wrefresh(edit);
2225
2226
	    break;
	case 1:
2227

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2228
	    buf = charealloc(buf, strlen(buf) + strlen(matches[0]) + 1);
2229

2230
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2231
2232
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2233
		tmp++;
2234
	    } else
2235
2236
		tmp = buf;

2237
	    if (strcmp(tmp, matches[0]) == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2238
		is_dir = append_slash_if_dir(buf, lastwastab, newplace);
2239

2240
	    if (is_dir != 0)
2241
		break;
2242
2243

	    copyto = tmp;
2244
2245
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2246
2247
		tmp++;

2248
	    /* write out the matched name */
2249
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2250
2251
	    *newplace += strlen(matches[0]) - pos;

2252
2253
2254
2255
2256
2257
2258
	    /* if an exact match is typed in and Tab is pressed,
	       *newplace will now be negative; in that case, make it
	       zero, so that the cursor will stay where it is instead of
	       moving backward */
	    if (*newplace < 0)
		*newplace = 0;

2259
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2260
	    append_slash_if_dir(buf, lastwastab, newplace);
2261

2262
2263
	    break;
	default:
2264
	    /* Check to see if all matches share a beginning, and, if so,
2265
	       tack it onto buf and then beep */
2266

2267
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2268
2269
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2270
		tmp++;
2271
	    } else
2272
2273
		tmp = buf;

2274
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2275
		 pos <= strlen(matches[0]); pos++)
2276
2277
		tmp++;

2278
	    while (TRUE) {
2279
2280
2281
2282
2283
2284
2285
2286
		match_matches = 0;

		for (i = 0; i < num_matches; i++) {
		    if (matches[i][pos] == 0)
			break;
		    else if (matches[i][pos] == matches[0][pos])
			match_matches++;
		}
2287
2288
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2289
		    /* All the matches have the same character at pos+1,
2290
		       so paste it into buf... */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2291
		    buf = charealloc(buf, strlen(buf) + 2);
2292
		    strncat(buf, matches[0] + pos, 1);
2293
		    *newplace += 1;
2294
		    pos++;
2295
		} else {
2296
2297
2298
2299
2300
		    beep();
		    break;
		}
	    }
	}
Chris Allegretta's avatar
Chris Allegretta committed
2301
2302
2303
2304
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2305
	if (matches != NULL && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2306
2307
2308
2309
2310

	    /* Blank the edit window, and print the matches out there */
	    blank_edit();
	    wmove(edit, 0, 0);

2311
	    editline = 0;
2312

2313
2314
2315
2316
2317
2318
2319
2320
	    /* Figure out the length of the longest filename */
	    for (i = 0; i < num_matches; i++)
		if (strlen(matches[i]) > longestname)
		    longestname = strlen(matches[i]);

	    if (longestname > COLS - 1)
		longestname = COLS - 1;

2321
	    foo = charalloc(longestname + 5);
2322

Chris Allegretta's avatar
Chris Allegretta committed
2323
2324
	    /* Print the list of matches */
	    for (i = 0, col = 0; i < num_matches; i++) {
2325

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2326
2327
		/* make each filename shown be the same length as the
		   longest filename, with two spaces at the end */
2328
		snprintf(foo, longestname + 1, "%s", matches[i]);
2329
2330
2331
2332
2333
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2334
2335
		/* Disable el cursor */
		curs_set(0);
2336
2337
2338
2339
2340
2341
		/* now, put the match on the screen */
		waddnstr(edit, foo, strlen(foo));
		col += strlen(foo);

		/* And if the next match isn't going to fit on the
		   line, move to the next one */
2342
		if (col > COLS - longestname && i + 1 < num_matches) {
2343
2344
		    editline++;
		    wmove(edit, editline, 0);
2345
2346
2347
2348
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2349
2350
2351
		    col = 0;
		}
	    }
2352
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2353
	    wrefresh(edit);
2354
	    *list = TRUE;
2355
2356
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2357
2358
    }

2359
2360
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
2361
    if (*list == FALSE)
2362
	edit_refresh();
2363
2364
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2365
}
2366
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2367

2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
/* Only print the last part of a path; isn't there a shell
 * command for this? */
const char *tail(const char *foo)
{
    const char *tmp = foo + strlen(foo);

    while (*tmp != '/' && tmp != foo)
	tmp--;

    if (*tmp == '/')
	tmp++;

    return tmp;
}

2383
#ifndef DISABLE_BROWSER
2384
/* Our sort routine for file listings -- sort directories before
2385
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2386
2387
int diralphasort(const void *va, const void *vb)
{
2388
2389
2390
2391
    struct stat fileinfo;
    const char *a = *(char *const *)va, *b = *(char *const *)vb;
    int aisdir = stat(a, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
    int bisdir = stat(b, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
Chris Allegretta's avatar
Chris Allegretta committed
2392

2393
    if (aisdir != 0 && bisdir == 0)
2394
	return -1;
2395
    if (aisdir == 0 && bisdir != 0)
2396
	return 1;
2397

2398
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2399
2400
}

2401
/* Free our malloc()ed memory */
2402
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2403
{
2404
2405
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2406
2407
2408
    free(array);
}

2409
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2410
2411
void striponedir(char *foo)
{
2412
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2413

2414
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2415
    /* Don't strip the root dir */
2416
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2417
2418
	return;

2419
2420
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2421
    if (*tmp == '/')
2422
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2423
2424
2425
2426
2427

    while (*tmp != '/' && tmp != foo)
	tmp--;

    if (tmp != foo)
2428
2429
2430
2431
2432
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2433
    }
Chris Allegretta's avatar
Chris Allegretta committed
2434
2435
}

2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
int readable_dir(const char *path)
{
    DIR *dir = opendir(path);

    /* If dir is NULL, don't do closedir(), since that changes errno. */
    if (dir != NULL)
	closedir(dir);
    return dir != NULL;
}

2446
/* Initialize the browser code, including the list of files in *path */
2447
char **browser_init(const char *path, int *longest, int *numents)
2448
2449
2450
{
    DIR *dir;
    struct dirent *next;
2451
    char **filelist;
2452
    int i = 0;
2453
    size_t path_len;
2454
2455

    dir = opendir(path);
2456
    if (dir == NULL)
2457
2458
2459
2460
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
2461
	if (strcmp(next->d_name, ".") == 0)
2462
2463
2464
2465
2466
2467
2468
2469
	   continue;
	(*numents)++;
	if (strlen(next->d_name) > *longest)
	    *longest = strlen(next->d_name);
    }
    rewinddir(dir);
    *longest += 10;

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

2472
    if (strcmp(path, "/") == 0)
2473
2474
2475
	path = "";
    path_len = strlen(path);

2476
    while ((next = readdir(dir)) != NULL) {
2477
	if (strcmp(next->d_name, ".") == 0)
2478
2479
	   continue;

2480
2481
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2482
2483
	i++;
    }
2484
    closedir(dir);
2485
2486
2487
2488
2489
2490
2491

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2492
/* Our browser function.  inpath is the path to start browsing from */
2493
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2494
2495
2496
2497
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2498
2499
2500
    int numents = 0, i = 0, j = 0, longest = 0, abort = 0, col = 0;
    int selected = 0, editline = 0, width = 0, filecols = 0, lineno = 0;
    int kbinput = ERR;
2501
    bool meta_key, func_key;
2502
    char **filelist = (char **)NULL;
2503
#ifndef DISABLE_MOUSE
2504
2505
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2506

2507
2508
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2509
2510
2511
    /* If path isn't the same as inpath, we are being passed a new
	dir as an arg.  We free it here so it will be copied from 
	inpath below */
2512
    if (path != NULL && strcmp(path, inpath) != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2513
2514
2515
2516
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2517
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2518
    if (path == NULL)
2519
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2520
2521

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

2524
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2525
2526
2527
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2528
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2529
2530
2531
2532
2533
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2534
2535

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2536
    do {
2537
2538
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2539

2540
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2541

2542
	currshortcut = browser_list;
2543

Chris Allegretta's avatar
Chris Allegretta committed
2544
2545
 	editline = 0;
	col = 0;
2546
	    
2547
	/* Compute line number we're on now, so we don't divide by zero later */
2548
2549
2550
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2551
2552

	switch (kbinput) {
2553

2554
#ifndef DISABLE_MOUSE
2555
	case KEY_MOUSE:
2556
	    if (getmouse(&mevent) == ERR)
2557
		return retval;
2558
2559
2560
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2561
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2562
2563
2564
2565
		int selectedbackup = selected;

		mevent.y -= 2;

2566
2567
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2568
		selected = (lineno / editwinrows) * editwinrows * width
2569
2570
2571
2572
2573
2574
			+ 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--;
2575
2576
2577

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2578
		if (selected > numents - 1)
2579
		    selected = numents - 1;
2580
		else if (selectedbackup == selected)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2581
		    /* Put back the 'select' key */
2582
		    unget_kbinput('s', FALSE, FALSE);
2583
2584
	    } else {
		/* Must be clicking a shortcut */
2585
2586
2587
		int mouse_x, mouse_y;
		get_mouseinput(&mouse_x, &mouse_y, TRUE);
	    }
2588

2589
2590
            break;
#endif
2591
	case NANO_PREVLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2592
2593
2594
	    if (selected - width >= 0)
		selected -= width;
	    break;
2595
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2596
2597
2598
	    if (selected > 0)
		selected--;
	    break;
2599
	case NANO_NEXTLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2600
2601
2602
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
2603
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2604
2605
2606
2607
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2608
	case NANO_PREVPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2609
	case '-': /* Pico compatibility */
2610
	    if (selected >= (editwinrows + lineno % editwinrows) * width)
2611
		selected -= (editwinrows + lineno % editwinrows) * width;
Chris Allegretta's avatar
Chris Allegretta committed
2612
2613
2614
2615
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2616
	case NANO_NEXTPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2617
	case ' ': /* Pico compatibility */
2618
2619
	    selected += (editwinrows - lineno % editwinrows) * width;
	    if (selected >= numents)
Chris Allegretta's avatar
Chris Allegretta committed
2620
2621
		selected = numents - 1;
	    break;
2622
2623
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2624
	case '?': /* Pico compatibility */
2625
#ifndef DISABLE_HELP
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2626
	    do_help();
2627
	    curs_set(0);
2628
2629
2630
#else
	    nano_disabled_msg();
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2631
	    break;
2632
	case NANO_ENTER_KEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2633
2634
	case 'S': /* Pico compatibility */
	case 's':
Chris Allegretta's avatar
Chris Allegretta committed
2635
	    /* You can't cd up from / */
2636
2637
	    if (strcmp(filelist[selected], "/..") == 0 &&
		strcmp(path, "/") == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2638
		statusbar(_("Can't move up a directory"));
2639
		beep();
Rocco Corsi's avatar
   
Rocco Corsi committed
2640
2641
2642
		break;
	    }

2643
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2644
2645
2646
	    /* Note: the selected file can be outside the operating
	     * directory if it is .. or if it is a symlink to 
	     * directory outside the operating directory. */
2647
	    if (check_operating_dir(filelist[selected], FALSE) != 0) {
2648
2649
		statusbar(_("Can't go outside of %s in restricted mode"),
			operating_dir);
2650
2651
		beep();
		break;
2652
2653
2654
	    }
#endif

2655
	    if (stat(filelist[selected], &st) == -1) {
2656
2657
		statusbar(_("Can't open \"%s\": %s"), filelist[selected],
			strerror(errno));
2658
2659
2660
		beep();
		break;
	    }
2661

2662
2663
2664
2665
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2666
2667
	    }

2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
	    new_path = mallocstrcpy(NULL, filelist[selected]);

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

2682
2683
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
2684
2685
		statusbar(_("Can't open \"%s\": %s"), new_path,
			strerror(errno));
2686
2687
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2688
	    }
2689
2690
2691
2692
2693
2694
2695

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

2696
	/* Go to a specific directory */
2697
2698
	case NANO_GOTOLINE_KEY:
	case NANO_GOTOLINE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2699
2700
	case 'G': /* Pico compatibility */
	case 'g':
Rocco Corsi's avatar
   
Rocco Corsi committed
2701
	    curs_set(1);
2702
	    j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2703
#ifndef NANO_SMALL
2704
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2705
#endif
2706
		_("Go To Directory"));
2707
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2708
2709
2710
	    curs_set(0);

	    if (j < 0) {
2711
		statusbar(_("Cancelled"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2712
2713
2714
		break;
	    }

2715
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2716

2717
2718
2719
	    if (new_path[0] != '/') {
		new_path = charealloc(new_path, strlen(path) + strlen(answer) + 2);
		sprintf(new_path, "%s/%s", path, answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2720
2721
	    }

2722
#ifndef DISABLE_OPERATINGDIR
2723
	    if (check_operating_dir(new_path, FALSE) != 0) {
2724
2725
2726
2727
2728
2729
2730
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		free(new_path);
		break;
	    }
#endif

	    if (!readable_dir(new_path)) {
Rocco Corsi's avatar
   
Rocco Corsi committed
2731
2732
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2733
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2734
		break;
2735
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2736
2737

	    /* Start over again with the new path value */
2738
2739
	    free_charptrarray(filelist, numents);
	    free(foo);
2740
2741
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2742
2743
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2744
	/* Stuff we want to abort the browser */
2745
	case NANO_CANCEL_KEY:
2746
	case NANO_EXIT_KEY:
2747
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2748
2749
	case 'E': /* Pico compatibility */
	case 'e':
2750
2751
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2752
2753
2754
2755
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2756
2757
	blank_edit();

2758
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
	    i = width * editwinrows * ((selected / width) / editwinrows);
	else
	    i = 0;

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

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

	    /* Put file info in the string also */
2773
2774
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2775
2776
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2777
2778
2779
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2780
2781
2782
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
2783
		    stat(filelist[j], &st);
2784
2785
2786
2787
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2788
2789
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2790
			(int) st.st_size);
2791
2792
2793
2794
2795
2796
		else if (st.st_size >= (1 << 30)) /* at least 1 gig */
		    sprintf(foo + longest - 7, "%4d GB", 
			(int) st.st_size >> 30);
		else if (st.st_size >= (1 << 20)) /* at least 1 meg */
		    sprintf(foo + longest - 7, "%4d MB", 
			(int) st.st_size >>     20);
2797
		else /* It's more than 1 k and less than a meg */
2798
2799
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2800
2801
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2802
	    /* Highlight the currently selected file/dir */
2803
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2804
		wattron(edit, A_REVERSE);
2805
2806
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2807
2808
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2809
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2810
2811
2812
2813
2814
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2815
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2816
2817
2818
2819
2820
2821
2822
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
2823
	wrefresh(edit);
2824
2825
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
Chris Allegretta's avatar
Chris Allegretta committed
2826
2827
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2828
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2829
2830
    edit_refresh();

Chris Allegretta's avatar
Chris Allegretta committed
2831
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2832
2833
2834
2835
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2836

2837
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2838
 starts do_browser from there, else from the current dir */
2839
char *do_browse_from(const char *inpath)
2840
2841
{
    struct stat st;
2842
    char *bob;
2843
2844
2845
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2846

2847
2848
2849
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2850

2851
2852
2853
2854
2855
2856
2857
2858
    /*
     * Perhaps path is a directory.  If so, we will pass that to
     * do_browser.  Otherwise, perhaps path is a directory / a file.  So
     * we try stripping off the last path element.  If it still isn't a
     * directory, just use the current directory. */

    if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	striponedir(path);
2859
2860
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2861
	    path = getcwd(NULL, PATH_MAX + 1);
2862
	}
2863
    }
2864

2865
2866
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2867
    if (check_operating_dir(path, FALSE) != 0)
2868
2869
2870
2871
2872
2873
2874
2875
2876
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2877
    return bob;
2878
}
Chris Allegretta's avatar
Chris Allegretta committed
2879
#endif /* !DISABLE_BROWSER */
2880

2881
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2882
2883
2884
/* 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)
2885
{
2886
    char *nanohist = NULL;
2887

2888
2889
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2890

2891
2892
2893
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2894
    }
2895
2896
2897
2898
2899
2900
    return nanohist;
}

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

2902
    /* assume do_rcfile() has reported missing home dir */
2903
2904
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2905

2906
	if (hist == NULL) {
2907
	    if (errno != ENOENT) {
2908
2909
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2910
		rcfile_error(N_("Error reading %s: %s"), nanohist, strerror(errno));
2911
2912
2913
		fprintf(stderr, _("\nPress Return to continue starting nano\n"));
		while (getchar() != '\n')
		    ;
2914
	    }
2915
	} else {
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
	    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
2930
2931
2932
		    history = &replace_history;
	    }
	    fclose(hist);
2933
	    free(line);
2934
2935
	    UNSET(HISTORY_CHANGED);
	}
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
	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;
2952
    }
2953
    return TRUE;
2954
2955
2956
2957
2958
}

/* save histories to ~/.nano_history */
void save_history(void)
{
2959
    char *nanohist;
2960
2961

    /* don't save unchanged or empty histories */
2962
    if ((search_history.count == 0 && replace_history.count == 0) ||
2963
	!ISSET(HISTORY_CHANGED) || ISSET(VIEW_MODE))
2964
2965
	return;

2966
2967
2968
2969
    nanohist = histfilename();

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

2971
	if (hist == NULL)
2972
	    rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2973
	else {
2974
2975
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2976
2977
2978
2979

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2980
		rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2981
2982
2983
2984
2985
	    fclose(hist);
	}
	free(nanohist);
    }
}
2986
#endif /* !NANO_SMALL && ENABLE_NANORC */