files.c 67.3 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-2004 Chris Allegretta                             *
6
 *   Copyright (C) 2005-2006 David Lawrence Ramsey                        *
Chris Allegretta's avatar
Chris Allegretta committed
7
8
 *   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 *
9
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
10
11
 *   any later version.                                                   *
 *                                                                        *
12
13
14
15
 *   This program is distributed in the hope that it will be useful, but  *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    *
 *   General Public License for more details.                             *
Chris Allegretta's avatar
Chris Allegretta committed
16
17
18
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
19
20
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
Chris Allegretta's avatar
Chris Allegretta committed
21
22
23
 *                                                                        *
 **************************************************************************/

24
#include "proto.h"
25

Chris Allegretta's avatar
Chris Allegretta committed
26
#include <stdio.h>
27
#include <string.h>
Chris Allegretta's avatar
Chris Allegretta committed
28
#include <unistd.h>
29
#include <utime.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
31
#include <fcntl.h>
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
32
#include <ctype.h>
33
#include <pwd.h>
Chris Allegretta's avatar
Chris Allegretta committed
34

35
36
37
/* Add an entry to the openfile openfilestruct.  This should only be
 * called from open_buffer(). */
void make_new_buffer(void)
38
{
39
40
    /* If there are no entries in openfile, make the first one and
     * move to it. */
41
42
43
    if (openfile == NULL) {
	openfile = make_new_opennode();
	splice_opennode(openfile, openfile, openfile);
44
45
46
    /* Otherwise, make a new entry for openfile, splice it in after
     * the current entry, and move to it. */
    } else {
47
48
	splice_opennode(openfile, make_new_opennode(), openfile->next);
	openfile = openfile->next;
Chris Allegretta's avatar
Chris Allegretta committed
49
    }
50

51
52
53
54
55
56
57
58
59
    /* Initialize the new buffer. */
    initialize_buffer();
}

/* Initialize the current entry of the openfile openfilestruct. */
void initialize_buffer(void)
{
    assert(openfile != NULL);

60
    openfile->filename = mallocstrcpy(NULL, "");
Chris Allegretta's avatar
Chris Allegretta committed
61

62
    initialize_buffer_text();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
63

64
65
    openfile->current_x = 0;
    openfile->placewewant = 0;
66
    openfile->current_y = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
67

68
    openfile->modified = FALSE;
69
#ifndef NANO_TINY
70
    openfile->mark_set = FALSE;
71

72
73
74
    openfile->mark_begin = NULL;
    openfile->mark_begin_x = 0;

75
    openfile->fmt = NIX_FILE;
76

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
77
    openfile->current_stat = NULL;
78
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
79
#ifdef ENABLE_COLOR
80
81
    openfile->colorstrings = NULL;
#endif
82
}
Chris Allegretta's avatar
Chris Allegretta committed
83

84
85
86
/* Initialize the text of the current entry of the openfile
 * openfilestruct. */
void initialize_buffer_text(void)
87
{
88
    assert(openfile != NULL);
89

90
91
    openfile->fileage = make_new_node(NULL);
    openfile->fileage->data = mallocstrcpy(NULL, "");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
92

93
94
95
96
97
    openfile->filebot = openfile->fileage;
    openfile->edittop = openfile->fileage;
    openfile->current = openfile->fileage;

    openfile->totsize = 0;
98
99
}

100
101
/* If it's not "", filename is a file to open.  We make a new buffer, if
 * necessary, and then open and read the file, if applicable. */
102
void open_buffer(const char *filename)
103
{
104
105
106
    bool new_buffer = (openfile == NULL
#ifdef ENABLE_MULTIBUFFER
	 || ISSET(MULTIBUFFER)
107
#endif
108
109
110
111
112
113
	);
	/* Whether we load into this buffer or a new one. */
    FILE *f;
    int rc;
	/* rc == -2 means that we have a new file.  -1 means that the
	 * open() failed.  0 means that the open() succeeded. */
114

115
116
    assert(filename != NULL);

117
118
119
120
121
122
123
#ifndef DISABLE_OPERATINGDIR
    if (check_operating_dir(filename, FALSE)) {
	statusbar(_("Can't insert file from outside of %s"),
		operating_dir);
	return;
    }
#endif
124

125
126
    /* If the filename isn't blank, open the file.  Otherwise, treat it
     * as a new file. */
127
128
    rc = (filename[0] != '\0') ? open_file(filename, new_buffer, &f) :
	-2;
129

130
131
    /* If we're loading into a new buffer, add a new entry to
     * openfile. */
132
133
    if (new_buffer)
	make_new_buffer();
134

135
136
137
138
    /* If we have a file and we're loading into a new buffer, update the
     * filename. */
    if (rc != -1 && new_buffer)
	openfile->filename = mallocstrcpy(openfile->filename, filename);
139

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
140
141
    /* If we have a non-new file, read it in.  Then, if the buffer has
     * no stat, update the stat, if applicable. */
142
143
    if (rc == 0) {
	read_file(f, filename);
144
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
145
146
147
148
149
	if (openfile->current_stat == NULL) {
	    openfile->current_stat =
		(struct stat *)nmalloc(sizeof(struct stat));
	    stat(filename, openfile->current_stat);
	}
150
#endif
151
152
    }

153
    /* If we have a file and we're loading into a new buffer, move back
154
155
     * to the beginning of the first line of the buffer. */
    if (rc != -1 && new_buffer) {
156
	openfile->current = openfile->fileage;
157
158
159
	openfile->current_x = 0;
	openfile->placewewant = 0;
    }
160
161

#ifdef ENABLE_COLOR
162
163
    /* If we're loading into a new buffer, update the colors to account
     * for it, if applicable. */
164
165
166
    if (new_buffer)
	color_update();
#endif
167
}
168

169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#ifndef DISABLE_SPELLER
/* If it's not "", filename is a file to open.  We blow away the text of
 * the current buffer, and then open and read the file, if
 * applicable.  Note that we skip the operating directory test when
 * doing this. */
void replace_buffer(const char *filename)
{
    FILE *f;
    int rc;
	/* rc == -2 means that we have a new file.  -1 means that the
	 * open() failed.  0 means that the open() succeeded. */

    assert(filename != NULL);

    /* If the filename isn't blank, open the file.  Otherwise, treat it
     * as a new file. */
    rc = (filename[0] != '\0') ? open_file(filename, TRUE, &f) : -2;

    /* Reinitialize the text of the current buffer. */
    free_filestruct(openfile->fileage);
    initialize_buffer_text();

    /* If we have a non-new file, read it in. */
    if (rc == 0)
	read_file(f, filename);

    /* Move back to the beginning of the first line of the buffer. */
    openfile->current = openfile->fileage;
    openfile->current_x = 0;
    openfile->placewewant = 0;
}
#endif /* !DISABLE_SPELLER */

202
/* Update the screen to account for the current buffer. */
203
void display_buffer(void)
204
{
205
    /* Update the titlebar, since the filename may have changed. */
206
    titlebar(NULL);
207
208

#ifdef ENABLE_COLOR
209
210
211
    /* Make sure we're using the buffer's associated colors, if
     * applicable. */
    color_init();
212
213
214
#endif

    /* Update the edit window. */
215
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
216
217
}

218
219
220
221
#ifdef ENABLE_MULTIBUFFER
/* Switch to the next file buffer if next_buf is TRUE.  Otherwise,
 * switch to the previous file buffer. */
void switch_to_prevnext_buffer(bool next_buf)
Chris Allegretta's avatar
Chris Allegretta committed
222
{
223
    assert(openfile != NULL);
224

225
226
    /* If only one file buffer is open, indicate it on the statusbar and
     * get out. */
227
    if (openfile == openfile->next) {
228
229
	statusbar(_("No more open file buffers"));
	return;
Chris Allegretta's avatar
Chris Allegretta committed
230
    }
231

232
233
    /* Switch to the next or previous file buffer, depending on the
     * value of next_buf. */
234
    openfile = next_buf ? openfile->next : openfile->prev;
235
236

#ifdef DEBUG
237
    fprintf(stderr, "filename is %s\n", openfile->filename);
238
239
#endif

240
    /* Update the screen to account for the current buffer. */
241
    display_buffer();
242

243
    /* Indicate the switch on the statusbar. */
244
    statusbar(_("Switched to %s"),
245
246
	((openfile->filename[0] == '\0') ? _("New Buffer") :
	openfile->filename));
247
248

#ifdef DEBUG
249
    dump_filestruct(openfile->current);
250
#endif
Chris Allegretta's avatar
Chris Allegretta committed
251
252
}

253
/* Switch to the previous entry in the openfile filebuffer. */
254
void switch_to_prev_buffer_void(void)
255
{
256
    switch_to_prevnext_buffer(FALSE);
257
}
258

259
/* Switch to the next entry in the openfile filebuffer. */
260
void switch_to_next_buffer_void(void)
261
{
262
    switch_to_prevnext_buffer(TRUE);
263
}
264

265
/* Delete an entry from the openfile filebuffer, and switch to the one
266
267
268
 * after it.  Return TRUE on success, or FALSE if there are no more open
 * file buffers. */
bool close_buffer(void)
269
{
270
    assert(openfile != NULL);
271

272
    /* If only one file buffer is open, get out. */
273
    if (openfile == openfile->next)
274
	return FALSE;
275

276
    /* Switch to the next file buffer. */
277
    switch_to_next_buffer_void();
278

279
    /* Close the file buffer we had open before. */
280
    unlink_opennode(openfile->prev);
281

282
    display_main_list();
283

284
    return TRUE;
285
}
286
#endif /* ENABLE_MULTIBUFFER */
287

288
289
290
291
292
293
/* We make a new line of text from buf.  buf is length buf_len.  If
 * first_line_ins is TRUE, then we put the new line at the top of the
 * file.  Otherwise, we assume prevnode is the last line of the file,
 * and put our line after prevnode. */
filestruct *read_line(char *buf, filestruct *prevnode, bool
	*first_line_ins, size_t buf_len)
294
{
295
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
296

297
298
299
    /* Convert nulls to newlines.  buf_len is the string's real length
     * here. */
    unsunder(buf, buf_len);
300

301
    assert(openfile->fileage != NULL && strlen(buf) == buf_len);
302

303
    fileptr->data = mallocstrcpy(NULL, buf);
304

305
#ifndef NANO_TINY
306
307
308
309
    /* If it's a DOS file ("\r\n"), and file conversion isn't disabled,
     * strip the '\r' part from fileptr->data. */
    if (!ISSET(NO_CONVERT) && buf_len > 0 && buf[buf_len - 1] == '\r')
	fileptr->data[buf_len - 1] = '\0';
310
#endif
311

312
    if (*first_line_ins == TRUE) {
313
314
315
	/* Special case: We're inserting with the cursor on the first
	 * line. */
	fileptr->prev = NULL;
316
	fileptr->next = openfile->fileage;
317
318
319
320
321
322
	fileptr->lineno = 1;
	if (*first_line_ins == TRUE) {
	    *first_line_ins = FALSE;
	    /* If we're inserting into the first line of the file, then
	     * we want to make sure that our edit buffer stays on the
	     * first line and that fileage stays up to date. */
323
	    openfile->edittop = fileptr;
324
	} else
325
326
	    openfile->filebot = fileptr;
	openfile->fileage = fileptr;
327
328
329
330
331
332
333
    } else {
	assert(prevnode != NULL);

	fileptr->prev = prevnode;
	fileptr->next = NULL;
	fileptr->lineno = prevnode->lineno + 1;
	prevnode->next = fileptr;
334
335
    }

336
337
    return fileptr;
}
338

339
340
/* Read an open file into the current buffer.  f should be set to the
 * open file, and filename should be set to the name of the file. */
341
342
343
344
345
346
347
348
349
350
351
352
353
354
void read_file(FILE *f, const char *filename)
{
    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 = MAX_BUF_SIZE;
	/* The size of each chunk of the file that we read. */
    char input = '\0';
	/* The current input character. */
    char *buf;
	/* The buffer where we store chunks of the file. */
355
    filestruct *fileptr = openfile->current;
356
357
358
359
360
361
	/* 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. */
362
#ifndef NANO_TINY
363
364
    int format = 0;
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
365
#endif
366

367
368
    assert(openfile->fileage != NULL && openfile->current != NULL);

369
370
    buf = charalloc(bufx);
    buf[0] = '\0';
371

372
373
374
375
    if (openfile->current == openfile->fileage)
	first_line_ins = TRUE;
    else
	fileptr = openfile->current->prev;
376

377
    /* Read the entire file into the filestruct. */
378
379
380
381
382
383
    while ((input_int = getc(f)) != EOF) {
	input = (char)input_int;

	/* If it's a *nix file ("\n") or a DOS file ("\r\n"), and file
	 * conversion isn't disabled, handle it! */
	if (input == '\n') {
384
#ifndef NANO_TINY
385
386
387
388
389
390
	    /* If there's a '\r' before the '\n', set format to DOS if
	     * we currently think this is a *nix file, or to both if we
	     * currently think it's a Mac file. */
	    if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r' &&
		(format == 0 || format == 2))
		format++;
391
#endif
392

393
394
	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
395

396
397
398
	    /* Reset the line length in preparation for the next
	     * line. */
	    len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
399

400
401
402
	    num_lines++;
	    buf[0] = '\0';
	    i = 0;
403
#ifndef NANO_TINY
404
405
406
407
408
409
410
411
	/* If it's a Mac file ('\r' without '\n'), and file conversion
	 * isn't disabled, handle it! */
	} else if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r') {
	    /* 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;
412

413
414
	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
415

416
417
418
419
	    /* 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. */
	    len = 1;
Chris Allegretta's avatar
Chris Allegretta committed
420

421
422
423
424
	    num_lines++;
	    buf[0] = input;
	    buf[1] = '\0';
	    i = 1;
425
#endif
426
427
428
429
	} else {
	    /* Calculate the total length of the line.  It might have
	     * nulls in it, so we can't just use strlen() here. */
	    len++;
430

431
432
433
434
435
436
437
438
	    /* Now we allocate a bigger buffer MAX_BUF_SIZE characters
	     * at a time.  If we allocate a lot of space for one line,
	     * we may indeed have to use a buffer this big later on, so
	     * we don't decrease it at all.  We do free it at the end,
	     * though. */
	    if (i >= bufx - 1) {
		bufx += MAX_BUF_SIZE;
		buf = charealloc(buf, bufx);
439
	    }
440
441
442
443
444
445
446
447
448
449
450

	    buf[i] = input;
	    buf[i + 1] = '\0';
	    i++;
	}
    }

    /* Perhaps this could use some better handling. */
    if (ferror(f))
	nperror(filename);
    fclose(f);
451

452
#ifndef NANO_TINY
453
454
455
456
457
458
459
460
    /* If file conversion isn't disabled and the last character in this
     * file is '\r', read it in properly as a Mac format line. */
    if (len == 0 && !ISSET(NO_CONVERT) && input == '\r') {
	len = 1;

	buf[0] = input;
	buf[1] = '\0';
    }
461
#endif
462

463
464
    /* Did we not get a newline and still have stuff to do? */
    if (len > 0) {
465
#ifndef NANO_TINY
466
467
468
469
470
471
472
	/* If file conversion isn't disabled and the last character in
	 * this file is '\r', set format to Mac if we currently think
	 * the file is a *nix file, or to both DOS and Mac if we
	 * currently think the file is a DOS file. */
	if (!ISSET(NO_CONVERT) && buf[len - 1] == '\r' &&
		(format == 0 || format == 1))
	    format += 2;
473
474
#endif

475
476
477
478
	/* Read in the last line properly. */
	fileptr = read_line(buf, fileptr, &first_line_ins, len);
	num_lines++;
    }
479

480
    free(buf);
481

482
    /* If we didn't get a file and we don't already have one, open a
483
     * blank buffer. */
484
    if (fileptr == NULL)
485
	open_buffer("");
486

487
488
    /* Attach the file we got to the filestruct.  If we got a file of
     * zero bytes, don't do anything. */
489
    if (num_lines > 0) {
490
491
492
493
494
495
496
497
498
499
500
501
	/* If the file we got doesn't end in a newline, tack its last
	 * line onto the beginning of the line at current. */
	if (len > 0) {
	    size_t current_len = strlen(openfile->current->data);

	    /* Adjust the current x-coordinate to compensate for the
	     * change in the current line. */
	    if (num_lines == 1)
		openfile->current_x += len;
	    else
		openfile->current_x = len;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
502
503
	    /* Tack the text at fileptr onto the beginning of the text
	     * at current. */
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
	    openfile->current->data =
		charealloc(openfile->current->data, len +
		current_len + 1);
	    charmove(openfile->current->data + len,
		openfile->current->data, current_len + 1);
	    strncpy(openfile->current->data, fileptr->data, len);

	    /* Don't destroy fileage, edittop, or filebot! */
	    if (fileptr == openfile->fileage)
		openfile->fileage = openfile->current;
	    if (fileptr == openfile->edittop)
		openfile->edittop = openfile->current;
	    if (fileptr == openfile->filebot)
		openfile->filebot = openfile->current;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
519
	    /* Move fileptr back one line and blow away the old fileptr,
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
	     * since its text has been saved. */
	    fileptr = fileptr->prev;
	    if (fileptr != NULL) {
		if (fileptr->next != NULL)
		    free(fileptr->next);
	    }
	}

	/* Attach the line at current after the line at fileptr. */
	if (fileptr != NULL) {
	    fileptr->next = openfile->current;
	    openfile->current->prev = fileptr;
	}

	/* Renumber starting with the last line of the file we
	 * inserted. */
536
	renumber(openfile->current);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
537
    }
538

539
540
    openfile->totsize += get_totsize(openfile->fileage,
	openfile->filebot);
541

542
543
544
545
546
    /* If the NO_NEWLINES flag isn't set, and text has been added to
     * the magicline (i.e, a file that doesn't end in a newline has been
     * inserted at the end of the current buffer), add a new magicline,
     * and move the current line down to it. */
    if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0') {
547
548
549
550
551
552
553
554
555
	new_magicline();
	openfile->current = openfile->filebot;
	openfile->current_x = 0;
    }

    /* Set the current place we want to the end of the last line of the
     * file we inserted. */
    openfile->placewewant = xplustabs();

556
#ifndef NANO_TINY
557
558
559
560
561
562
    if (format == 3)
	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);
    else if (format == 2) {
563
	openfile->fmt = MAC_FILE;
564
565
566
567
	statusbar(P_("Read %lu line (Converted from Mac format)",
		"Read %lu lines (Converted from Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
    } else if (format == 1) {
568
	openfile->fmt = DOS_FILE;
569
570
571
572
	statusbar(P_("Read %lu line (Converted from DOS format)",
		"Read %lu lines (Converted from DOS format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
    } else
573
#endif
574
575
576
	statusbar(P_("Read %lu line", "Read %lu lines",
		(unsigned long)num_lines), (unsigned long)num_lines);
}
Chris Allegretta's avatar
Chris Allegretta committed
577

578
579
580
581
582
583
584
585
586
587
588
589
/* 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)
{
    int fd;
    struct stat fileinfo;

590
    assert(filename != NULL && f != NULL);
591

592
    if (stat(filename, &fileinfo) == -1) {
593
594
595
596
597
	if (newfie) {
	    statusbar(_("New File"));
	    return -2;
	}
	statusbar(_("\"%s\" not found"), filename);
598
	beep();
599
600
	return -1;
    } else if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
601
	S_ISBLK(fileinfo.st_mode)) {
602
603
	/* Don't open directories, character files, or block files.
	 * Sorry, /dev/sndstat! */
604
605
	statusbar(S_ISDIR(fileinfo.st_mode) ?
		_("\"%s\" is a directory") :
606
		_("\"%s\" is a device file"), filename);
607
	beep();
608
609
610
	return -1;
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
	statusbar(_("Error reading %s: %s"), filename, strerror(errno));
611
	beep();
612
613
 	return -1;
     } else {
614
	/* The file is A-OK.  Open it. */
615
	*f = fdopen(fd, "rb");
616

617
618
619
	if (*f == NULL) {
	    statusbar(_("Error reading %s: %s"), filename,
		strerror(errno));
620
	    beep();
621
622
623
	    close(fd);
	} else
	    statusbar(_("Reading File"));
624
    }
625

626
    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
627
628
}

629
630
631
632
633
/* This function will return the name of the first available extension
 * of a filename (starting with [name][suffix], then [name][suffix].1,
 * etc.).  Memory is allocated for the return value.  If no writable
 * extension exists, we return "". */
char *get_next_filename(const char *name, const char *suffix)
634
{
635
    static int ulmax_digits = -1;
636
637
638
    unsigned long i = 0;
    char *buf;
    size_t namelen, suffixlen;
639

640
    assert(name != NULL && suffix != NULL);
641

642
643
644
    if (ulmax_digits == -1)
	ulmax_digits = digits(ULONG_MAX);

645
646
    namelen = strlen(name);
    suffixlen = strlen(suffix);
Chris Allegretta's avatar
Chris Allegretta committed
647

648
    buf = charalloc(namelen + suffixlen + ulmax_digits + 2);
649
    sprintf(buf, "%s%s", name, suffix);
650

651
652
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
653

654
655
656
657
	if (stat(buf, &fs) == -1)
	    return buf;
	if (i == ULONG_MAX)
	    break;
658

659
660
661
	i++;
	sprintf(buf + namelen + suffixlen, ".%lu", i);
    }
Chris Allegretta's avatar
Chris Allegretta committed
662

663
664
665
    /* We get here only if there is no possible save file.  Blank out
     * the filename to indicate this. */
    null_at(&buf, 0);
666

667
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
668
669
}

670
671
672
/* Insert a file into a new buffer if the MULTIBUFFER flag is set, or
 * into the current buffer if it isn't.  If execute is TRUE, insert the
 * output of an executed command instead of a file. */
673
void do_insertfile(
674
#ifndef NANO_TINY
675
676
677
678
679
680
681
682
683
684
	bool execute
#else
	void
#endif
	)
{
    int i;
    const char *msg;
    char *ans = mallocstrcpy(NULL, "");
	/* The last answer the user typed on the statusbar. */
685
686
    filestruct *edittop_save = openfile->edittop;
    ssize_t current_y_save = openfile->current_y;
687
688
    bool at_edittop = FALSE;
	/* Whether we're at the top of the edit window. */
689

690
    while (TRUE) {
691
#ifndef NANO_TINY
692
	if (execute) {
693
	    msg = 
694
#ifdef ENABLE_MULTIBUFFER
695
		ISSET(MULTIBUFFER) ? 
696
		_("Command to execute in new buffer [from %s] ") :
697
#endif
698
		_("Command to execute [from %s] ");
699
700
	} else {
#endif
701
	    msg =
702
#ifdef ENABLE_MULTIBUFFER
703
		ISSET(MULTIBUFFER) ? 
704
		_("File to insert into new buffer [from %s] ") :
705
#endif
706
		_("File to insert [from %s] ");
707
#ifndef NANO_TINY
708
709
	}
#endif
710

711
	i = do_prompt(TRUE,
712
713
714
#ifndef DISABLE_TABCOMP
		TRUE,
#endif
715
#ifndef NANO_TINY
716
		execute ? extcmd_list :
717
#endif
718
		insertfile_list, ans,
719
#ifndef NANO_TINY
720
		NULL,
721
#endif
722
		edit_refresh, msg,
723
#ifndef DISABLE_OPERATINGDIR
724
725
		operating_dir != NULL && strcmp(operating_dir,
		".") != 0 ? operating_dir :
726
727
#endif
		"./");
728

729
730
731
732
733
734
735
736
737
738
	/* If we're in multibuffer mode and the filename or command is
	 * blank, open a new buffer instead of canceling. */
	if (i == -1 || (i == -2
#ifdef ENABLE_MULTIBUFFER
		&& !ISSET(MULTIBUFFER)
#endif
		)) {
	    statusbar(_("Cancelled"));
	    break;
	} else {
739
	    size_t pww_save = openfile->placewewant;
740

741
	    ans = mallocstrcpy(ans, answer);
742

743
#ifndef NANO_TINY
744
745
746
747
748
749
750
#ifdef ENABLE_MULTIBUFFER
	    if (i == TOGGLE_MULTIBUFFER_KEY) {
		/* Don't allow toggling if we're in view mode. */
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
		continue;
	    } else
751
#endif
752
753
754
755
756
757
758
	    if (i == NANO_TOOTHERINSERT_KEY) {
		execute = !execute;
		continue;
	    }
#ifndef DISABLE_BROWSER
	    else
#endif
759
#endif /* !NANO_TINY */
760

761
762
763
#ifndef DISABLE_BROWSER
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
764

765
766
		if (tmp == NULL)
		    continue;
767

768
769
		free(answer);
		answer = tmp;
770

771
772
773
		/* We have a file now.  Indicate this and get out of the
		 * statusbar prompt cleanly. */
		i = 0;
774
		do_prompt_abort();
775
776
	    }
#endif
777

778
779
780
781
782
783
784
785
	    /* If we don't have a file yet, go back to the statusbar
	     * prompt. */
	    if (i != 0
#ifdef ENABLE_MULTIBUFFER
		&& (i != -2 || !ISSET(MULTIBUFFER))
#endif
		)
		continue;
786

787
788
789
790
791
792
793
794
#ifdef ENABLE_MULTIBUFFER
	    if (!ISSET(MULTIBUFFER)) {
#endif
		/* If we're not inserting into a new buffer, partition
		 * the filestruct so that it contains no text and hence
		 * looks like a new buffer, and keep track of whether
		 * the top of the partition is the top of the edit
		 * window. */
795
796
797
798
799
		filepart = partition_filestruct(openfile->current,
			openfile->current_x, openfile->current,
			openfile->current_x);
		at_edittop =
			(openfile->fileage == openfile->edittop);
800
801
802
#ifdef ENABLE_MULTIBUFFER
	    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
803

804
#ifndef NANO_TINY
805
806
807
	    if (execute) {
#ifdef ENABLE_MULTIBUFFER
		if (ISSET(MULTIBUFFER))
808
		    /* Open a blank buffer. */
809
810
811
812
		    open_buffer("");
#endif

		/* Save the command's output in the current buffer. */
813
		execute_command(answer);
814
	    } else {
815
#endif
816
817
		/* Make sure the path to the file specified in answer is
		 * tilde-expanded. */
818
819
		answer = mallocstrassn(answer,
			real_dir_from_tilde(answer));
820
821
822

		/* Save the file specified in answer in the current
		 * buffer. */
823
		open_buffer(answer);
824
#ifndef NANO_TINY
825
	    }
Chris Allegretta's avatar
Chris Allegretta committed
826
#endif
827

828
#ifdef ENABLE_MULTIBUFFER
829
830
831
	    if (ISSET(MULTIBUFFER))
		/* Update the screen to account for the current
		 * buffer. */
832
		display_buffer();
833
	    else
Chris Allegretta's avatar
Chris Allegretta committed
834
#endif
835
	    {
836
		filestruct *top_save = openfile->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
837

838
839
840
841
842
843
		/* 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)
844
845
		    edittop_save = openfile->fileage;
		openfile->current_y += current_y_save;
846

847
848
849
850
851
852
		/* If we didn't insert into a new buffer, unpartition
		 * 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. */
		unpartition_filestruct(&filepart);
853

854
855
856
		/* Renumber starting with the beginning line of the old
		 * partition. */
		renumber(top_save);
857

858
		/* Restore the old edittop. */
859
		openfile->edittop = edittop_save;
860

861
862
863
		/* Restore the old place we want. */
		openfile->placewewant = pww_save;

864
865
		/* Mark the file as modified. */
		set_modified();
866

867
868
		/* Update the screen. */
		edit_refresh();
869
	    }
870

871
872
873
874
875
	    break;
	}
    }

    free(ans);
876
877
}

878
879
880
/* Insert a file into a new buffer or the current buffer, depending on
 * whether the MULTIBUFFER flag is set.  If we're in view mode, only
 * allow inserting a file into a new buffer. */
881
void do_insertfile_void(void)
882
{
883
884
885
886
887
888
#ifdef ENABLE_MULTIBUFFER
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
	statusbar(_("Key illegal in non-multibuffer mode"));
    else
#endif
	do_insertfile(
889
#ifndef NANO_TINY
890
891
892
		FALSE
#endif
		);
893
894
895
896

    display_main_list();
}

897
/* When passed "[relative path]" or "[relative path][filename]" in
898
 * origpath, return "[full path]" or "[full path][filename]" on success,
899
900
901
902
 * or NULL on error.  Do this if the file doesn't exist but the relative
 * path does, since the file could exist in memory but not yet on disk).
 * Don't do this if the relative path doesn't exist, since we won't be
 * able to go there. */
903
char *get_full_path(const char *origpath)
904
{
905
    char *d_here, *d_there = NULL;
906

907
908
    if (origpath == NULL)
    	return NULL;
909

910
911
912
    /* Get the current directory. */
    d_here = charalloc(PATH_MAX + 1);
    d_here = getcwd(d_here, PATH_MAX + 1);
913

914
    if (d_here != NULL) {
915
916
917
918
	const char *last_slash;
	char *d_there_file = NULL;
	bool path_only;
	struct stat fileinfo;
919

920
921
	align(&d_here);

922
923
	/* If the current directory isn't "/", tack a slash onto the end
	 * of it. */
924
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
925
	    d_here = charealloc(d_here, strlen(d_here) + 2);
926
927
	    strcat(d_here, "/");
	}
928

929
930
931
932
933
934
935
936
937
938
939
940
	d_there = real_dir_from_tilde(origpath);

	assert(d_there != NULL);

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

	/* If path_only is TRUE, make sure d_there ends in a slash. */
941
	if (path_only) {
942
943
944
945
	    size_t d_there_len = strlen(d_there);

	    if (d_there[d_there_len - 1] != '/') {
		d_there = charealloc(d_there, d_there_len + 2);
946
947
948
949
		strcat(d_there, "/");
	    }
	}

950
	/* Search for the last slash in d_there. */
951
952
	last_slash = strrchr(d_there, '/');

953
954
955
956
	/* If we didn't find one, then make sure the answer is in the
	 * format "d_here/d_there". */
	if (last_slash == NULL) {
	    assert(!path_only);
957

958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
	    d_there_file = d_there;
	    d_there = d_here;
	} else {
	    /* If path_only is FALSE, then save the filename portion of
	     * the answer, everything after the last slash, in
	     * d_there_file. */
	    if (!path_only)
		d_there_file = mallocstrcpy(NULL, last_slash + 1);

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

	    /* Go to the path specified in d_there. */
	    if (chdir(d_there) == -1) {
973
		free(d_there);
974
975
976
977
		d_there = NULL;
	    } else {
		/* Get the full path and save it in d_there. */
		free(d_there);
978

979
980
981
		d_there = charalloc(PATH_MAX + 1);
		d_there = getcwd(d_there, PATH_MAX + 1);

982
983
984
985
986
987
988
989
990
991
		if (d_there != NULL) {
		    align(&d_there);

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

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

1002
1003
	    /* Free d_here, since we're done using it. */
	    free(d_here);
1004
	}
1005

1006
1007
1008
1009
1010
	/* At this point, if path_only is FALSE and d_there exists,
	 * d_there contains the path portion of the answer and
	 * d_there_file contains the filename portion of the answer.  If
	 * this is the case, tack d_there_file onto the end of
	 * d_there, so that d_there contains the complete answer. */
1011
	if (!path_only && d_there != NULL) {
1012
1013
1014
1015
1016
1017
	    d_there = charealloc(d_there, strlen(d_there) +
		strlen(d_there_file) + 1);
	    strcat(d_there, d_there_file);
 	}

	/* Free d_there_file, since we're done using it. */
1018
1019
1020
	free(d_there_file);
    }

1021
    return d_there;
1022
}
1023

1024
1025
1026
/* Return the full version of path, as returned by get_full_path().  On
 * error, if path doesn't reference a directory, or if the directory
 * isn't writable, return NULL. */
1027
char *check_writable_directory(const char *path)
1028
{
1029
1030
    char *full_path = get_full_path(path);

1031
    /* If get_full_path() fails, return NULL. */
1032
    if (full_path == NULL)
1033
	return NULL;
1034

1035
1036
1037
    /* If we can't write to path or path isn't a directory, return
     * NULL. */
    if (access(full_path, W_OK) != 0 ||
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1038
	full_path[strlen(full_path) - 1] != '/') {
1039
	free(full_path);
1040
	return NULL;
1041
    }
1042

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1043
    /* Otherwise, return the full path. */
1044
1045
1046
    return full_path;
}

1047
1048
/* This function calls mkstemp(($TMPDIR|P_tmpdir|/tmp/)"nano.XXXXXX").
 * On success, it returns the malloc()ed filename and corresponding FILE
1049
 * stream, opened in "r+b" mode.  On error, it returns NULL for the
1050
1051
 * filename and leaves the FILE stream unchanged. */
char *safe_tempfile(FILE **f)
1052
{
1053
    char *full_tempdir = NULL;
1054
1055
1056
    const char *tmpdir_env;
    int fd;
    mode_t original_umask = 0;
1057

1058
1059
    assert(f != NULL);

1060
1061
1062
    /* If $TMPDIR is set, set tempdir to it, run it through
     * get_full_path(), and save the result in full_tempdir.  Otherwise,
     * leave full_tempdir set to NULL. */
1063
    tmpdir_env = getenv("TMPDIR");
1064
    if (tmpdir_env != NULL)
1065
	full_tempdir = check_writable_directory(tmpdir_env);
1066

1067
1068
    /* If $TMPDIR is unset, empty, or not a writable directory, and
     * full_tempdir is NULL, try P_tmpdir instead. */
1069
    if (full_tempdir == NULL)
1070
	full_tempdir = check_writable_directory(P_tmpdir);
1071

1072
1073
1074
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1075

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1076
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1077
    strcat(full_tempdir, "nano.XXXXXX");
1078

1079
1080
1081
1082
1083
1084
1085
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

    if (fd != -1)
	*f = fdopen(fd, "r+b");
1086
1087
1088
    else {
	free(full_tempdir);
	full_tempdir = NULL;
1089
    }
1090

1091
1092
    umask(original_umask);

1093
    return full_tempdir;
1094
}
1095
1096

#ifndef DISABLE_OPERATINGDIR
1097
1098
1099
1100
1101
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1102
    if (operating_dir == NULL)
1103
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1104

1105
1106
1107
    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
1108
     * inaccessible, unset operating_dir. */
1109
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1110
1111
1112
1113
1114
1115
1116
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1117
1118
1119
1120
1121
/* Check to see if we're inside the operating directory.  Return FALSE
 * if we are, or TRUE otherwise.  If allow_tabcomp is TRUE, allow
 * incomplete names that would be matches for the operating directory,
 * so that tab completion will work. */
bool check_operating_dir(const char *currpath, bool allow_tabcomp)
1122
{
1123
1124
1125
1126
    /* 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. */
1127

1128
    char *fullpath;
1129
    bool retval = FALSE;
1130
    const char *whereami1, *whereami2 = NULL;
1131

1132
    /* If no operating directory is set, don't bother doing anything. */
1133
    if (operating_dir == NULL)
1134
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1135

1136
    assert(full_operating_dir != NULL);
1137
1138

    fullpath = get_full_path(currpath);
1139
1140

    /* fullpath == NULL means some directory in the path doesn't exist
1141
     * or is unreadable.  If allow_tabcomp is FALSE, then currpath is
1142
1143
     * what the user typed somewhere.  We don't want to report a
     * non-existent directory as being outside the operating directory,
1144
     * so we return FALSE.  If allow_tabcomp is TRUE, then currpath
1145
1146
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1147
    if (fullpath == NULL)
1148
	return allow_tabcomp;
1149
1150
1151
1152
1153

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

1154
    /* If both searches failed, we're outside the operating directory.
1155
     * Otherwise, check the search results.  If the full operating
1156
1157
1158
     * 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. */
1159
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1160
1161
	retval = TRUE;
    free(fullpath);
1162
1163

    /* Otherwise, we're still inside it. */
1164
    return retval;
1165
}
1166
1167
#endif

1168
#ifndef NANO_TINY
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
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

1192
/* Read from inn, write to out.  We assume inn is opened for reading,
1193
1194
 * and out for writing.  We return 0 on success, -1 on read error, or -2
 * on write error. */
1195
1196
int copy_file(FILE *inn, FILE *out)
{
1197
    char buf[BUFSIZ];
1198
1199
1200
1201
    size_t charsread;
    int retval = 0;

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

1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
    do {
	charsread = fread(buf, sizeof(char), BUFSIZ, inn);
	if (charsread == 0 && ferror(inn)) {
	    retval = -1;
	    break;
	}
	if (fwrite(buf, sizeof(char), charsread, out) < charsread) {
	    retval = -2;
	    break;
	}
    } while (charsread > 0);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1214

1215
1216
1217
1218
    if (fclose(inn) == EOF)
	retval = -1;
    if (fclose(out) == EOF)
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1219

1220
1221
1222
    return retval;
}

1223
1224
1225
/* Write a file out.  If f_open isn't NULL, we assume that it is a
 * stream associated with the file, and we don't try to open it
 * ourselves.  If tmp is TRUE, we set the umask to disallow anyone else
1226
1227
 * from accessing the file, we don't set the 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
1228
 *
1229
1230
 * 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.
1231
 *
1232
1233
 * append == APPEND means we are appending instead of overwriting.
 * append == PREPEND means we are prepending instead of overwriting.
1234
 *
1235
1236
 * nonamechange means don't change the current filename.  It is ignored
 * if tmp is FALSE or if we're appending/prepending.
1237
 *
1238
 * Return 0 on success or -1 on error. */
1239
1240
int write_file(const char *name, FILE *f_open, bool tmp, append_type
	append, bool nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1241
{
1242
1243
    int retval = -1;
	/* Instead of returning in this function, you should always
1244
1245
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
1246
    const filestruct *fileptr = openfile->fileage;
1247
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1248
	/* The file descriptor we use. */
1249
    mode_t original_umask = 0;
1250
	/* Our umask, from when nano started. */
1251
    bool realexists;
1252
	/* The result of stat().  TRUE if the file exists, FALSE
1253
	 * otherwise.  If name is a link that points nowhere, realexists
1254
	 * is FALSE. */
1255
1256
    struct stat st;
	/* The status fields filled in by stat(). */
1257
    bool anyexists;
1258
1259
	/* The result of lstat().  The same as realexists, unless name
	 * is a link. */
1260
1261
1262
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1263
	/* name after tilde expansion. */
1264
    FILE *f = NULL;
1265
1266
1267
	/* 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
1268

1269
    assert(name != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1270

1271
    if (name[0] == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1272
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1273

1274
1275
1276
    if (f_open != NULL)
	f = f_open;

1277
1278
    if (!tmp)
	titlebar(NULL);
1279

1280
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1281

1282
#ifndef DISABLE_OPERATINGDIR
1283
    /* If we're writing a temporary file, we're probably going outside
1284
     * the operating directory, so skip the operating directory test. */
1285
    if (!tmp && check_operating_dir(realname, FALSE)) {
1286
1287
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1288
1289
1290
    }
#endif

1291
    anyexists = (lstat(realname, &lst) != -1);
1292

1293
1294
    /* If the temp file exists and isn't already open, give up. */
    if (tmp && anyexists && f_open == NULL)
1295
	goto cleanup_and_exit;
1296

1297
1298
1299
    /* 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)) {
1300
1301
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1302
1303
1304
	goto cleanup_and_exit;
    }

1305
    /* Save the state of the file at the end of the symlink (if there is
1306
     * one). */
1307
    realexists = (stat(realname, &st) != -1);
1308

1309
#ifndef NANO_TINY
1310
    /* We backup only if the backup toggle is set, the file isn't
1311
1312
1313
1314
     * 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. */
1315
1316
1317
    if (ISSET(BACKUP_FILE) && !tmp && realexists && ((append !=
	OVERWRITE || openfile->mark_set) ||
	openfile->current_stat->st_mtime == st.st_mtime)) {
1318
	FILE *backup_file;
1319
	char *backupname;
1320
	struct utimbuf filetime;
1321
	int copy_status;
1322

1323
	/* Save the original file's access and modification times. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1324
1325
	filetime.actime = openfile->current_stat->st_atime;
	filetime.modtime = openfile->current_stat->st_mtime;
1326

1327
1328
1329
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1330

1331
1332
1333
	    if (f == NULL) {
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1334
		beep();
1335
1336
1337
1338
		/* If we can't read from the original file, go on, since
		 * only saving the original file is better than saving
		 * nothing. */
		goto skip_backup;
1339
	    }
1340
1341
	}

1342
	/* If backup_dir is set, we set backupname to
1343
1344
1345
1346
	 * backup_dir/backupname~[.number], where backupname 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~[.number]. */
1347
	if (backup_dir != NULL) {
1348
	    char *backuptemp = get_full_path(realname);
1349

1350
	    if (backuptemp == NULL)
1351
1352
1353
1354
1355
1356
		/* 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~. */
1357
		backuptemp = mallocstrcpy(NULL, tail(realname));
1358
	    else {
1359
1360
		size_t i = 0;

1361
1362
1363
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1364
1365
1366
1367
		}
	    }

	    backupname = charalloc(strlen(backup_dir) +
1368
1369
1370
1371
1372
1373
1374
1375
1376
		strlen(backuptemp) + 1);
	    sprintf(backupname, "%s%s", backup_dir, backuptemp);
	    free(backuptemp);
	    backuptemp = get_next_filename(backupname, "~");
	    if (backuptemp[0] == '\0') {
		statusbar(_("Error writing %s: %s"), backupname,
		    _("Too many backup files?"));
		free(backuptemp);
		free(backupname);
1377
1378
1379
1380
		/* If we can't write to the backup, go on, since only
		 * saving the original file is better than saving
		 * nothing. */
		goto skip_backup;
1381
1382
1383
1384
	    } else {
		free(backupname);
		backupname = backuptemp;
	    }
1385
1386
1387
1388
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1389

1390
1391
1392
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1393
	backup_file = fopen(backupname, "wb");
1394

1395
	if (backup_file == NULL || chmod(backupname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1396
		openfile->current_stat->st_mode) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1397
1398
	    statusbar(_("Error writing %s: %s"), backupname,
		strerror(errno));
1399
	    free(backupname);
1400
1401
	    if (backup_file != NULL)
		fclose(backup_file);
1402
1403
1404
	    /* If we can't write to the backup, go on, since only saving
	     * the original file is better than saving nothing. */
	    goto skip_backup;
1405
1406
1407
	}

#ifdef DEBUG
1408
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1409
1410
#endif

1411
1412
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1413

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1414
	/* And set its metadata. */
1415
	if (copy_status != 0 || chown(backupname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1416
1417
		openfile->current_stat->st_uid,
		openfile->current_stat->st_gid) == -1 ||
1418
		utime(backupname, &filetime) == -1) {
1419
	    if (copy_status == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1420
1421
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1422
1423
		beep();
	    } else
1424
1425
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
1426
1427
1428
	    /* If we can't read from or write to the backup, go on,
	     * since only saving the original file is better than saving
	     * nothing. */
1429
	}
1430

1431
1432
	free(backupname);
    }
1433
1434

  skip_backup:
1435
#endif /* !NANO_TINY */
1436

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1437
1438
    /* If NOFOLLOW_SYMLINKS is set and the file is a link, we aren't
     * doing prepend or append.  So we delete the link first, and just
1439
1440
1441
1442
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1443
	goto cleanup_and_exit;
1444
    }
1445

1446
1447
    if (f_open == NULL) {
	original_umask = umask(0);
1448

1449
	/* If we create a temp file, we don't let anyone else access it.
1450
1451
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1452
	    umask(S_IRWXG | S_IRWXO);
1453
1454
	else
	    umask(original_umask);
1455
    }
1456

1457
    /* If we're prepending, copy the file to a temp file. */
1458
    if (append == PREPEND) {
1459
1460
1461
	int fd_source;
	FILE *f_source = NULL;

1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
	if (f == NULL) {
	    f = fopen(realname, "rb");

	    if (f == NULL) {
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
		beep();
		goto cleanup_and_exit;
	    }
	}

1473
	tempname = safe_tempfile(&f);
1474

1475
	if (tempname == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1476
	    statusbar(_("Error writing temp file: %s"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1477
		strerror(errno));
1478
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1479
	}
1480

1481
1482
	if (f_open == NULL) {
	    fd_source = open(realname, O_RDONLY | O_CREAT);
1483

1484
1485
1486
1487
1488
	    if (fd_source != -1) {
		f_source = fdopen(fd_source, "rb");
		if (f_source == NULL) {
		    statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1489
		    beep();
1490
1491
1492
1493
1494
1495
		    close(fd_source);
		    fclose(f);
		    unlink(tempname);
		    goto cleanup_and_exit;
		}
	    }
1496
1497
1498
	}

	if (copy_file(f_source, f) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1499
1500
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1501
	    unlink(tempname);
1502
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1503
1504
1505
	}
    }

1506
1507
1508
    if (f_open == NULL) {
	/* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This
	 * is copied from joe, because wiggy says so *shrug*. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1509
1510
1511
	fd = open(realname, O_WRONLY | O_CREAT | ((append == APPEND) ?
		O_APPEND : (tmp ? O_EXCL : O_TRUNC)), S_IRUSR |
		S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1512

1513
1514
	/* Set the umask back to the user's original value. */
	umask(original_umask);
1515

1516
1517
1518
1519
	/* If we couldn't open the file, give up. */
	if (fd == -1) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1520

1521
1522
1523
1524
1525
	    /* tempname has been set only if we're prepending. */
	    if (tempname != NULL)
		unlink(tempname);
	    goto cleanup_and_exit;
	}
1526

1527
	f = fdopen(fd, (append == APPEND) ? "ab" : "wb");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1528

1529
1530
1531
1532
1533
1534
	if (f == NULL) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
	    close(fd);
	    goto cleanup_and_exit;
	}
1535
1536
    }

1537
    /* There might not be a magicline.  There won't be when writing out
1538
     * a selection. */
1539
    assert(openfile->fileage != NULL && openfile->filebot != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1540

1541
    while (fileptr != NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1542
	size_t data_len = strlen(fileptr->data), size;
1543

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

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

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

1552
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1553
1554
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1555
	    fclose(f);
1556
	    goto cleanup_and_exit;
1557
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1558

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1559
	/* If we're on the last line of the file, don't write a newline
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1560
1561
1562
1563
1564
1565
1566
	 * character after it.  If the last line of the file is blank,
	 * this means that zero bytes are written, in which case we
	 * don't count the last line in the total lines written. */
	if (fileptr == openfile->filebot) {
	    if (fileptr->data[0] == '\0')
		lineswritten--;
	} else {
1567
#ifndef NANO_TINY
1568
1569
1570
1571
	    if (openfile->fmt == DOS_FILE || openfile->fmt ==
		MAC_FILE) {
		if (putc('\r', f) == EOF) {
		    statusbar(_("Error writing %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1572
			strerror(errno));
1573
1574
1575
		    fclose(f);
		    goto cleanup_and_exit;
		}
1576
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1577

1578
	    if (openfile->fmt != MAC_FILE) {
1579
#endif
1580
1581
		if (putc('\n', f) == EOF) {
		    statusbar(_("Error writing %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1582
			strerror(errno));
1583
1584
1585
		    fclose(f);
		    goto cleanup_and_exit;
		}
1586
#ifndef NANO_TINY
1587
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1588
#endif
1589
	}
Chris Allegretta's avatar
Chris Allegretta committed
1590
1591
1592
1593
1594

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

1595
    /* If we're prepending, open the temp file, and append it to f. */
1596
    if (append == PREPEND) {
1597
1598
1599
1600
	int fd_source;
	FILE *f_source = NULL;

	fd_source = open(tempname, O_RDONLY | O_CREAT);
1601

1602
1603
1604
1605
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1606
	}
1607

1608
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1609
1610
	    statusbar(_("Error reading %s: %s"), tempname,
		strerror(errno));
1611
	    beep();
1612
	    fclose(f);
1613
	    goto cleanup_and_exit;
1614
1615
	}

1616
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1617
1618
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1619
	    goto cleanup_and_exit;
1620
	}
1621
1622
    } else
	fclose(f);
1623

1624
    if (!tmp && append == OVERWRITE) {
Chris Allegretta's avatar
Chris Allegretta committed
1625
	if (!nonamechange) {
1626
1627
	    openfile->filename = mallocstrcpy(openfile->filename,
		realname);
Chris Allegretta's avatar
Chris Allegretta committed
1628
#ifdef ENABLE_COLOR
1629
	    /* We might have changed the filename, so update the colors
1630
1631
	     * to account for it, and then make sure we're using
	     * them. */
1632
	    color_update();
1633
	    color_init();
1634
1635
1636
1637
1638

	    /* If color syntaxes are available and turned on, we need to
	     * call edit_refresh(). */
	    if (openfile->colorstrings != NULL &&
		!ISSET(NO_COLOR_SYNTAX))
1639
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1640
1641
#endif
	}
1642

1643
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1644
1645
1646
1647
1648
	/* Update current_stat to reference the file as it is now. */
	if (openfile->current_stat == NULL)
	    openfile->current_stat =
		(struct stat *)nmalloc(sizeof(struct stat));
	stat(realname, openfile->current_stat);
1649
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1650

1651
1652
	statusbar(P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten),
1653
		(unsigned long)lineswritten);
1654
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1655
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1656
    }
1657

1658
    retval = 0;
1659
1660
1661

  cleanup_and_exit:
    free(realname);
1662
    free(tempname);
1663

1664
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1665
1666
}

1667
#ifndef NANO_TINY
1668
/* Write a marked selection from a file out. */
1669
1670
int write_marked_file(const char *name, FILE *f_open, bool tmp,
	append_type append)
1671
1672
{
    int retval = -1;
1673
1674
    bool old_modified = openfile->modified;
	/* write_file() unsets the modified flag. */
1675
    bool added_magicline = FALSE;
1676
1677
1678
1679
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

1680
1681
    assert(openfile->mark_set);

1682
1683
1684
    /* Partition the filestruct so that it contains only the marked
     * text. */
    mark_order((const filestruct **)&top, &top_x,
1685
	(const filestruct **)&bot, &bot_x, NULL);
1686
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1687

1688
1689
1690
1691
1692
1693
    /* Handle the magicline if the NO_NEWLINES flag isn't set.  If the
     * line at filebot is blank, treat it as the magicline and hence the
     * end of the file.  Otherwise, add a magicline and treat it as the
     * end of the file. */
    if (!ISSET(NO_NEWLINES) &&
	(added_magicline = (openfile->filebot->data[0] != '\0')))
1694
	new_magicline();
1695

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

1698
1699
1700
    /* If the NO_NEWLINES flag isn't set, and we added a magicline,
     * remove it now. */
    if (!ISSET(NO_NEWLINES) && added_magicline)
1701
1702
1703
1704
	remove_magicline();

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

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

    return retval;
}
1712
#endif /* !NANO_TINY */
1713

1714
1715
1716
1717
/* Write the current file to disk.  If the mark is on, write the current
 * marked selection to disk.  If exiting is TRUE, write the file to disk
 * regardless of whether the mark is on, and without prompting if the
 * TEMP_FILE flag is set. */
1718
int do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1719
{
1720
1721
    int i, retval = 0;
    append_type append = OVERWRITE;
1722
1723
    char *ans;
	/* The last answer the user typed on the statusbar. */
1724
#ifdef NANO_EXTRA
1725
    static bool did_credits = FALSE;
1726
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1727

1728
    currshortcut = writefile_list;
1729

1730
    if (exiting && openfile->filename[0] != '\0' && ISSET(TEMP_FILE)) {
1731
	retval = write_file(openfile->filename, NULL, FALSE, OVERWRITE,
1732
		FALSE);
1733
1734

	/* Write succeeded. */
1735
	if (retval == 0)
1736
	    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1737
1738
    }

1739
    ans = mallocstrcpy(NULL,
1740
#ifndef NANO_TINY
1741
	(openfile->mark_set && !exiting) ? "" :
1742
#endif
1743
	openfile->filename);
1744
1745
1746

    while (TRUE) {
	const char *msg;
1747
#ifndef NANO_TINY
1748
1749
	const char *formatstr, *backupstr;

1750
	formatstr = (openfile->fmt == DOS_FILE) ?
1751
1752
		_(" [DOS Format]") : (openfile->fmt == MAC_FILE) ?
		_(" [Mac Format]") : "";
1753

1754
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
1755

1756
	if (openfile->mark_set && !exiting)
1757
	    msg = (append == PREPEND) ?
1758
1759
1760
		_("Prepend Selection to File") : (append == APPEND) ?
		_("Append Selection to File") :
		_("Write Selection to File");
1761
	else
1762
#endif /* !NANO_TINY */
1763
1764
1765
	    msg = (append == PREPEND) ? _("File Name to Prepend to") :
		(append == APPEND) ? _("File Name to Append to") :
		_("File Name to Write");
1766

1767
1768
1769
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
1770
	i = do_prompt(!ISSET(RESTRICTED) ||
1771
1772
1773
1774
1775
		openfile->filename[0] == '\0',
#ifndef DISABLE_TABCOMP
		TRUE,
#endif
		writefile_list, ans,
1776
#ifndef NANO_TINY
1777
1778
		NULL,
#endif
1779
		edit_refresh, "%s%s%s", msg,
1780
1781
#ifndef NANO_TINY
		formatstr, backupstr
1782
#else
1783
		"", ""
1784
1785
1786
#endif
		);

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

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

1798
1799
		if (tmp == NULL)
		    continue;
1800

1801
1802
1803
1804
1805
		free(answer);
		answer = tmp;

		/* We have a file now.  Get out of the statusbar prompt
		 * cleanly. */
1806
		do_prompt_abort();
1807
	    } else
1808
#endif /* !DISABLE_BROWSER */
1809
#ifndef NANO_TINY
1810
	    if (i == TOGGLE_DOS_KEY) {
1811
1812
		openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE :
			DOS_FILE;
1813
1814
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1815
1816
		openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE :
			MAC_FILE;
1817
1818
1819
1820
1821
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1822
#endif /* !NANO_TINY */
1823
	    if (i == NANO_PREPEND_KEY) {
1824
		append = (append == PREPEND) ? OVERWRITE : PREPEND;
1825
1826
		continue;
	    } else if (i == NANO_APPEND_KEY) {
1827
		append = (append == APPEND) ? OVERWRITE : APPEND;
1828
1829
		continue;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1830

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

#ifdef NANO_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1836
1837
1838
1839
1840
1841
1842
1843
	    /* If the current file has been modified, we've pressed
	     * Ctrl-X at the edit window to exit, we've pressed "y" at
	     * the "Save modified buffer" prompt to save, we've entered
	     * "zzy" as the filename to save under (hence "xyzzy"), and
	     * this is the first time we've done this, show an Easter
	     * egg.  Display the credits. */
	    if (!did_credits && exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0) {
1844
		do_credits();
1845
		did_credits = TRUE;
1846
1847
1848
		retval = -1;
		break;
	    }
1849
#endif
1850
	    if (append == OVERWRITE && strcmp(answer,
1851
		openfile->filename) != 0) {
1852
1853
1854
		struct stat st;

		if (!stat(answer, &st)) {
1855
1856
		    i = do_yesno_prompt(FALSE,
			_("File exists, OVERWRITE ? "));
1857
1858
1859
		    if (i == 0 || i == -1)
			continue;
		/* If we're using restricted mode, we aren't allowed to
1860
		 * change the name of a file once it has one, because
1861
1862
1863
		 * 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. */
1864
1865
		} else if (!ISSET(RESTRICTED) &&
			openfile->filename[0] != '\0'
1866
#ifndef NANO_TINY
1867
			&& (exiting || !openfile->mark_set)
1868
#endif
1869
			) {
1870
		    i = do_yesno_prompt(FALSE,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1871
			_("Save file under DIFFERENT NAME ? "));
1872
1873
1874
		    if (i == 0 || i == -1)
			continue;
		}
1875
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1876

1877
#ifndef NANO_TINY
1878
1879
1880
1881
	    /* 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. */
1882
	    if (!ISSET(RESTRICTED) && !exiting && openfile->mark_set)
1883
		retval = write_marked_file(answer, NULL, FALSE, append);
1884
	    else
1885
#endif /* !NANO_TINY */
1886
		retval = write_file(answer, NULL, FALSE, append, FALSE);
1887

1888
1889
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1890
    }
1891
1892

    free(ans);
1893

1894
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1895
1896
}

1897
1898
/* Write the current file to disk.  If the mark is on, write the current
 * marked selection to disk. */
1899
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1900
{
1901
    do_writeout(FALSE);
1902
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1903
}
Chris Allegretta's avatar
Chris Allegretta committed
1904

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

1911
    assert(buf != NULL);
1912

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

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

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

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

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

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

1947
    return dirtmp;
1948
1949
}

1950
#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
1951
/* Our sort routine for file listings.  Sort alphabetically and
1952
 * case-insensitively, and sort directories before filenames. */
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
int diralphasort(const void *va, const void *vb)
{
    struct stat fileinfo;
    const char *a = *(const char *const *)va;
    const char *b = *(const char *const *)vb;
    bool aisdir = stat(a, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
    bool bisdir = stat(b, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);

    if (aisdir && !bisdir)
	return -1;
    if (!aisdir && bisdir)
	return 1;

1966
1967
1968
1969
    /* Standard function brain damage: We should be sorting
     * alphabetically and case-insensitively according to the current
     * locale, but there's no standard strcasecoll() function, so we
     * have to use multibyte strcasecmp() instead, */
1970
    return mbstrcasecmp(a, b);
1971
}
1972
1973
1974
1975
1976

/* Free the memory allocated for array, which should contain len
 * elements. */
void free_chararray(char **array, size_t len)
{
1977
1978
    assert(array != NULL);

1979
1980
1981
1982
    for (; len > 0; len--)
	free(array[len - 1]);
    free(array);
}
1983
1984
#endif

Chris Allegretta's avatar
Chris Allegretta committed
1985
#ifndef DISABLE_TABCOMP
1986
1987
/* Is the given file a directory? */
int is_dir(const char *buf)
1988
{
1989
    char *dirptr = real_dir_from_tilde(buf);
1990
    struct stat fileinfo;
1991
    int retval = (stat(dirptr, &fileinfo) != -1 &&
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1992
	S_ISDIR(fileinfo.st_mode));
1993

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

1996
    free(dirptr);
1997

1998
    return retval;
1999
}
Chris Allegretta's avatar
Chris Allegretta committed
2000

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

2019
2020
2021
2022
/* We consider the first buflen characters of buf for ~username tab
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
	size_t buflen)
Chris Allegretta's avatar
Chris Allegretta committed
2023
{
2024
2025
    char **matches = NULL;
    const struct passwd *userdata;
2026

2027
    assert(buf != NULL && num_matches != NULL && buflen > 0);
2028

2029
    *num_matches = 0;
2030

2031
    while ((userdata = getpwent()) != NULL) {
2032
2033
2034
	if (strncmp(userdata->pw_name, buf + 1, buflen - 1) == 0) {
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2035

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2043
2044
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
2045
2046
2047
	    matches[*num_matches] =
		charalloc(strlen(userdata->pw_name) + 2);
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2048
	    ++(*num_matches);
2049
	}
2050
2051
    }
    endpwent();
2052

2053
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2054
2055
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2056
2057
/* We consider the first buflen characters of buf for filename tab
 * completion. */
2058
2059
char **cwd_tab_completion(const char *buf, bool allow_files, size_t
	*num_matches, size_t buflen)
Chris Allegretta's avatar
Chris Allegretta committed
2060
{
2061
    char *dirname = mallocstrcpy(NULL, buf), *filename;
2062
2063
2064
2065
2066
#ifndef DISABLE_OPERATINGDIR
    size_t dirnamelen;
#endif
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2067
    DIR *dir;
2068
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2069

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

2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
    *num_matches = 0;
    null_at(&dirname, buflen);

    /* Okie, if there's a / in the buffer, strip out the directory
     * part. */
    filename = strrchr(dirname, '/');
    if (filename != NULL) {
	char *tmpdirname = filename + 1;

	filename = mallocstrcpy(NULL, tmpdirname);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2082
	*tmpdirname = '\0';
2083
2084
2085
	tmpdirname = dirname;
	dirname = real_dir_from_tilde(dirname);
	free(tmpdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2086
    } else {
2087
2088
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2089
2090
    }

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

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

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

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

2108
    while ((nextdir = readdir(dir)) != NULL) {
2109
2110
	bool skip_match = FALSE;

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

2121
2122
2123
2124
	    char *tmp = charalloc(strlen(dirname) +
		strlen(nextdir->d_name) + 1);
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

2125
2126
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2127
2128
2129
2130
2131
2132
2133
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(tmp, TRUE))
		skip_match = TRUE;
#endif

	    /* ...or unless the match isn't a directory and allow_files
	     * isn't set, in which case just go to the next match. */
2134
	    if (!allow_files && !is_dir(tmp))
2135
2136
2137
		skip_match = TRUE;

	    free(tmp);
2138

2139
	    if (skip_match)
2140
		continue;
2141

2142
2143
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
2144
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2145
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2146
2147
	}
    }
2148

2149
2150
    closedir(dir);
    free(dirname);
2151
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2152

2153
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2154
2155
}

2156
/* Do tab completion.  place refers to how much the statusbar cursor
2157
2158
 * position should be advanced.  refresh_func is the function we will
 * call to refresh the edit window. */
2159
char *input_tab(char *buf, bool allow_files, size_t *place, bool
2160
	*lastwastab, void (*refresh_func)(void), bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2161
{
2162
2163
    size_t num_matches = 0;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2164

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2167
    *list = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2168

2169
2170
2171
2172
    /* If the word starts with `~' and there is no slash in the word,
     * then try completing this word as a username. */
    if (*place > 0 && *buf == '~') {
	const char *bob = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2173

2174
2175
2176
2177
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2178

2179
2180
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
2181
2182
	matches = cwd_tab_completion(buf, allow_files, &num_matches,
		*place);
2183

2184
2185
2186
2187
2188
    if (num_matches <= 0)
	beep();
    else {
	size_t match, common_len = 0;
	char *mzero;
2189
2190
2191
	const char *lastslash = revstrstr(buf, "/", buf + *place);
	size_t lastslash_len = (lastslash == NULL) ? 0 :
		lastslash - buf + 1;
2192
2193
2194
	char *match1_mb = charalloc(mb_cur_max() + 1);
	char *match2_mb = charalloc(mb_cur_max() + 1);
	int match1_mb_len, match2_mb_len;
2195
2196
2197

	while (TRUE) {
	    for (match = 1; match < num_matches; match++) {
2198
2199
		/* Get the number of single-byte characters that all the
		 * matches have in common. */
2200
		match1_mb_len = parse_mbchar(matches[0] + common_len,
2201
			match1_mb, NULL);
2202
		match2_mb_len = parse_mbchar(matches[match] +
2203
			common_len, match2_mb, NULL);
2204
2205
2206
		match1_mb[match1_mb_len] = '\0';
		match2_mb[match2_mb_len] = '\0';
		if (strcmp(match1_mb, match2_mb) != 0)
2207
2208
		    break;
	    }
2209

2210
	    if (match < num_matches || matches[0][common_len] == '\0')
2211
		break;
2212

2213
	    common_len += parse_mbchar(buf + common_len, NULL, NULL);
2214
	}
2215

2216
2217
2218
	free(match1_mb);
	free(match2_mb);

2219
2220
	mzero = charalloc(lastslash_len + common_len + 1);
	sprintf(mzero, "%.*s%.*s", lastslash_len, buf, common_len,
2221
		matches[0]);
2222

2223
	common_len += lastslash_len;
2224

2225
	assert(common_len >= *place);
2226

2227
2228
2229
	if (num_matches == 1 && is_dir(mzero)) {
	    mzero[common_len] = '/';
	    common_len++;
2230

2231
2232
	    assert(common_len > *place);
	}
2233

2234
2235
2236
	if (num_matches > 1 && (common_len != *place ||
		*lastwastab == FALSE))
	    beep();
2237

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2238
	/* If there is more of a match to display on the statusbar, show
2239
	 * it.  We reset lastwastab to FALSE: it requires pressing Tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2240
	 * twice in succession with no statusbar changes to see a match
2241
2242
	 * list. */
	if (common_len != *place) {
2243
	    size_t buf_len = strlen(buf);
2244
2245

	    *lastwastab = FALSE;
2246
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2247
2248
	    charmove(buf + common_len, buf + *place, buf_len -
		*place + 1);
2249
	    strncpy(buf, mzero, common_len);
2250
	    *place = common_len;
2251
2252
2253
	} else if (*lastwastab == FALSE || num_matches < 2)
	    *lastwastab = TRUE;
	else {
2254
	    int longest_name = 0, columns, editline = 0;
2255

2256
2257
2258
2259
2260
2261
2262
2263
	    /* Now we show a list of the available choices. */
	    assert(num_matches > 1);

	    /* Sort the list. */
	    qsort(matches, num_matches, sizeof(char *), diralphasort);

	    for (match = 0; match < num_matches; match++) {
		common_len = strnlenpt(matches[match], COLS - 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2264

2265
2266
		if (common_len > COLS - 1) {
		    longest_name = COLS - 1;
2267
2268
		    break;
		}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2269

2270
2271
		if (common_len > longest_name)
		    longest_name = common_len;
2272
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2273

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

2276
2277
2278
	    /* Each column will be (longest_name + 2) columns wide, i.e,
	     * two spaces between columns, except that there will be
	     * only one space after the last column. */
2279
	    columns = (COLS + 1) / (longest_name + 2);
2280

2281
2282
2283
2284
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2285

2286
2287
	    /* Disable el cursor. */
	    curs_set(0);
2288

2289
2290
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2291

2292
2293
		wmove(edit, editline, (longest_name + 2) *
			(match % columns));
2294

2295
2296
2297
		if (match % columns == 0 &&
			editline == editwinrows - 1 &&
			num_matches - match > columns) {
2298
2299
2300
		    waddstr(edit, _("(more)"));
		    break;
		}
2301

2302
2303
2304
2305
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2306

2307
		if ((match + 1) % columns == 0)
2308
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2309
	    }
2310

2311
	    wnoutrefresh(edit);
2312
	    *list = TRUE;
2313
2314
2315
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2316
2317
    }

2318
    free_chararray(matches, num_matches);
2319

2320
    /* Only refresh the edit window if we don't have a list of filename
2321
     * matches on it. */
2322
    if (*list == FALSE)
2323
	refresh_func();
2324
2325

    /* Enable el cursor. */
2326
    curs_set(1);
2327

2328
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2329
}
2330
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2331

2332
2333
/* Only print the last part of a path.  Isn't there a shell command for
 * this? */
2334
2335
const char *tail(const char *foo)
{
2336
    const char *tmp = strrchr(foo, '/');
2337

2338
2339
2340
    if (tmp == NULL)
	tmp = foo;
    else if (*tmp == '/')
2341
2342
2343
2344
2345
	tmp++;

    return tmp;
}

2346
#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
2347
2348
2349
/* 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)
2350
{
2351
    char *nanohist = NULL;
2352

2353
2354
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2355

2356
2357
2358
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2359
    }
2360
2361
2362
    return nanohist;
}

2363
/* Load histories from ~/.nano_history. */
2364
2365
2366
void load_history(void)
{
    char *nanohist = histfilename();
Chris Allegretta's avatar
Chris Allegretta committed
2367

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2368
    /* Assume do_rcfile() has reported a missing home directory. */
2369
    if (nanohist != NULL) {
2370
	FILE *hist = fopen(nanohist, "rb");
Chris Allegretta's avatar
Chris Allegretta committed
2371

2372
	if (hist == NULL) {
2373
	    if (errno != ENOENT) {
2374
2375
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2376
2377
2378
		rcfile_error(N_("Error reading %s: %s"), nanohist,
			strerror(errno));
		fprintf(stderr,
2379
			_("\nPress Enter to continue starting nano.\n"));
2380
2381
		while (getchar() != '\n')
		    ;
2382
	    }
2383
	} else {
2384
2385
2386
2387
	    /* Load a history (first the search history, then the
	     * replace history) from oldest to newest.  Assume the last
	     * history entry is a blank line. */
	    filestruct **history = &search_history;
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
	    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);
2399
2400
		    update_history(history, line);
		} else
2401
2402
		    history = &replace_history;
	    }
2403

2404
	    fclose(hist);
2405
	    free(line);
2406
	}
2407
2408
2409
2410
	free(nanohist);
    }
}

2411
2412
2413
/* Write the lines of a history list, starting with the line at h, to
 * the open file at hist.  Return TRUE if the write succeeded, and FALSE
 * otherwise. */
2414
bool writehist(FILE *hist, filestruct *h)
2415
{
2416
    filestruct *p;
2417

2418
2419
2420
    /* Write history from oldest to newest.  Assume the last history
     * entry is a blank line. */
    for (p = h; p != NULL; p = p->next) {
2421
	size_t p_len = strlen(p->data);
2422

2423
	sunder(p->data);
2424

2425
	if (fwrite(p->data, sizeof(char), p_len, hist) < p_len ||
2426
2427
		putc('\n', hist) == EOF)
	    return FALSE;
2428
    }
2429

2430
    return TRUE;
2431
2432
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2433
/* Save histories to ~/.nano_history. */
2434
2435
void save_history(void)
{
2436
    char *nanohist;
2437

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2438
    /* Don't save unchanged or empty histories. */
2439
2440
    if (!history_has_changed() || (searchbot->lineno == 1 &&
	replacebot->lineno == 1))
2441
2442
	return;

2443
2444
2445
2446
    nanohist = histfilename();

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

2448
	if (hist == NULL)
2449
2450
	    rcfile_error(N_("Error writing %s: %s"), nanohist,
		strerror(errno));
2451
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2452
2453
	    /* Make sure no one else can read from or write to the
	     * history file. */
2454
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2455

2456
2457
	    if (!writehist(hist, searchage) || !writehist(hist,
		replaceage))
2458
2459
		rcfile_error(N_("Error writing %s: %s"), nanohist,
			strerror(errno));
2460

2461
2462
	    fclose(hist);
	}
2463

2464
2465
2466
	free(nanohist);
    }
}
2467
#endif /* !NANO_TINY && ENABLE_NANORC */