files.c 64.8 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
 *   any later version.                                                   *
 *                                                                        *
11
12
13
14
 *   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
15
16
17
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
18
19
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
Chris Allegretta's avatar
Chris Allegretta committed
20
21
22
 *                                                                        *
 **************************************************************************/

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

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

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

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

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

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

64
    initialize_buffer_text();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
65

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

70
    openfile->modified = FALSE;
71
#ifndef NANO_SMALL
72
    openfile->mark_set = FALSE;
73

74
75
76
    openfile->mark_begin = NULL;
    openfile->mark_begin_x = 0;

77
    openfile->fmt = NIX_FILE;
78

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

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

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

95
96
97
98
99
    openfile->filebot = openfile->fileage;
    openfile->edittop = openfile->fileage;
    openfile->current = openfile->fileage;

    openfile->totsize = 0;
100
101
}

102
103
/* 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. */
104
void open_buffer(const char *filename)
105
{
106
107
108
    bool new_buffer = (openfile == NULL
#ifdef ENABLE_MULTIBUFFER
	 || ISSET(MULTIBUFFER)
109
#endif
110
111
112
113
114
115
	);
	/* 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. */
116

117
118
    assert(filename != NULL);

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

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

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

137
138
139
140
    /* 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);
141

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

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

#ifdef ENABLE_COLOR
164
165
    /* If we're loading into a new buffer, update the colors to account
     * for it, if applicable. */
166
167
168
    if (new_buffer)
	color_update();
#endif
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
202
203
#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 */

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

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

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

220
221
222
223
#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
224
{
225
    assert(openfile != NULL);
226

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

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

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

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

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

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

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

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

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

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

278
    /* Switch to the next file buffer. */
279
    switch_to_next_buffer_void();
280

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

284
    display_main_list();
285

286
    return TRUE;
287
}
288
#endif /* ENABLE_MULTIBUFFER */
289

290
291
292
293
294
295
/* 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)
296
{
297
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
298

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

303
    assert(openfile->fileage != NULL && strlen(buf) == buf_len);
304

305
    fileptr->data = mallocstrcpy(NULL, buf);
306

Chris Allegretta's avatar
Chris Allegretta committed
307
#ifndef NANO_SMALL
308
309
310
311
    /* 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';
312
#endif
313

314
    if (*first_line_ins == TRUE) {
315
316
317
	/* Special case: We're inserting with the cursor on the first
	 * line. */
	fileptr->prev = NULL;
318
	fileptr->next = openfile->fileage;
319
320
321
322
323
324
	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. */
325
	    openfile->edittop = fileptr;
326
	} else
327
328
	    openfile->filebot = fileptr;
	openfile->fileage = fileptr;
329
330
331
332
333
334
335
    } else {
	assert(prevnode != NULL);

	fileptr->prev = prevnode;
	fileptr->next = NULL;
	fileptr->lineno = prevnode->lineno + 1;
	prevnode->next = fileptr;
336
337
    }

338
339
    return fileptr;
}
340

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_SMALL
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') {
Chris Allegretta's avatar
Chris Allegretta committed
384
#ifndef NANO_SMALL
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_SMALL
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_SMALL
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
465
466
467
468
469
470
471
472
    /* Did we not get a newline and still have stuff to do? */
    if (len > 0) {
#ifndef NANO_SMALL
	/* 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
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
	    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;

	    /* Move fileptr back one line and delete the old fileptr,
	     * 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
557
558
559
560
561
562
#ifndef NANO_SMALL
    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
598
599
	if (newfie) {
	    statusbar(_("New File"));
	    return -2;
	}
	statusbar(_("\"%s\" not found"), filename);
	return -1;
    } else if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
600
	S_ISBLK(fileinfo.st_mode)) {
601
	/* Don't open character or block files.  Sorry, /dev/sndstat! */
602
603
604
	statusbar(S_ISDIR(fileinfo.st_mode) ?
		_("\"%s\" is a directory") :
		_("File \"%s\" is a device file"), filename);
605
606
607
608
609
610
611
612
	return -1;
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
	statusbar(_("Error reading %s: %s"), filename, strerror(errno));
 	return -1;
     } else {
	/* File is A-OK.  Open it in binary mode for our own end-of-line
	 * character munging. */
	*f = fdopen(fd, "rb");
613

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

622
    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
623
624
}

625
626
627
628
629
/* 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)
630
{
631
632
633
    unsigned long i = 0;
    char *buf;
    size_t namelen, suffixlen;
634

635
    assert(name != NULL && suffix != NULL);
636

637
638
    namelen = strlen(name);
    suffixlen = strlen(suffix);
Chris Allegretta's avatar
Chris Allegretta committed
639

640
641
    buf = charalloc(namelen + suffixlen + digits(ULONG_MAX) + 2);
    sprintf(buf, "%s%s", name, suffix);
642

643
644
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
645

646
647
648
649
	if (stat(buf, &fs) == -1)
	    return buf;
	if (i == ULONG_MAX)
	    break;
650

651
652
653
	i++;
	sprintf(buf + namelen + suffixlen, ".%lu", i);
    }
Chris Allegretta's avatar
Chris Allegretta committed
654

655
656
657
    /* We get here only if there is no possible save file.  Blank out
     * the filename to indicate this. */
    null_at(&buf, 0);
658

659
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
660
661
}

662
663
664
665
666
667
668
669
670
671
672
673
void do_insertfile(
#ifndef NANO_SMALL
	bool execute
#else
	void
#endif
	)
{
    int i;
    const char *msg;
    char *ans = mallocstrcpy(NULL, "");
	/* The last answer the user typed on the statusbar. */
674
675
    filestruct *edittop_save = openfile->edittop;
    ssize_t current_y_save = openfile->current_y;
676
677
    bool at_edittop = FALSE;
	/* Whether we're at the top of the edit window. */
678

679
680
681
#ifndef DISABLE_WRAPPING
    wrap_reset();
#endif
682

683
684
685
    while (TRUE) {
#ifndef NANO_SMALL
	if (execute) {
686
	    msg = 
687
#ifdef ENABLE_MULTIBUFFER
688
689
		ISSET(MULTIBUFFER) ? 
		N_("Command to execute in new buffer [from %s] ") :
690
#endif
691
		N_("Command to execute [from %s] ");
692
693
	} else {
#endif
694
	    msg =
695
#ifdef ENABLE_MULTIBUFFER
696
697
		ISSET(MULTIBUFFER) ? 
		N_("File to insert into new buffer [from %s] ") :
698
#endif
699
		N_("File to insert [from %s] ");
700
701
702
#ifndef NANO_SMALL
	}
#endif
703

704
	i = do_prompt(TRUE,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
705
#ifndef NANO_SMALL
706
		execute ? extcmd_list :
707
#endif
708
709
710
		insertfile_list, ans,
#ifndef NANO_SMALL
		NULL,
711
#endif
712
713
714
715
716
717
		_(msg),
#ifndef DISABLE_OPERATINGDIR
		operating_dir != NULL && strcmp(operating_dir, ".") != 0 ?
		operating_dir :
#endif
		"./");
718

719
720
721
722
723
724
725
726
727
728
	/* 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 {
729
	    size_t pww_save = openfile->placewewant;
730

731
	    ans = mallocstrcpy(ans, answer);
732

733
#ifndef NANO_SMALL
734
735
736
737
738
739
740
#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
741
#endif
742
743
744
745
746
747
748
749
	    if (i == NANO_TOOTHERINSERT_KEY) {
		execute = !execute;
		continue;
	    }
#ifndef DISABLE_BROWSER
	    else
#endif
#endif /* !NANO_SMALL */
750

751
752
753
#ifndef DISABLE_BROWSER
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
754

755
756
		if (tmp == NULL)
		    continue;
757

758
759
		free(answer);
		answer = tmp;
760

761
762
763
		/* We have a file now.  Indicate this and get out of the
		 * statusbar prompt cleanly. */
		i = 0;
764
		do_prompt_abort();
765
766
	    }
#endif
767

768
769
770
771
772
773
774
775
	    /* 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;
776

777
778
779
780
781
782
783
784
#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. */
785
786
787
788
789
		filepart = partition_filestruct(openfile->current,
			openfile->current_x, openfile->current,
			openfile->current_x);
		at_edittop =
			(openfile->fileage == openfile->edittop);
790
791
792
#ifdef ENABLE_MULTIBUFFER
	    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
793
794

#ifndef NANO_SMALL
795
796
797
	    if (execute) {
#ifdef ENABLE_MULTIBUFFER
		if (ISSET(MULTIBUFFER))
798
		    /* Open a blank buffer. */
799
800
801
802
		    open_buffer("");
#endif

		/* Save the command's output in the current buffer. */
803
		execute_command(answer);
804
	    } else {
805
#endif
806
807
		/* Make sure the path to the file specified in answer is
		 * tilde-expanded. */
808
809
		answer = mallocstrassn(answer,
			real_dir_from_tilde(answer));
810
811
812

		/* Save the file specified in answer in the current
		 * buffer. */
813
		open_buffer(answer);
814
815
#ifndef NANO_SMALL
	    }
Chris Allegretta's avatar
Chris Allegretta committed
816
#endif
817

818
#ifdef ENABLE_MULTIBUFFER
819
820
821
	    if (ISSET(MULTIBUFFER))
		/* Update the screen to account for the current
		 * buffer. */
822
		display_buffer();
823
	    else
Chris Allegretta's avatar
Chris Allegretta committed
824
#endif
825
	    {
826
		filestruct *top_save = openfile->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
827

828
829
830
831
832
833
		/* 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)
834
835
		    edittop_save = openfile->fileage;
		openfile->current_y += current_y_save;
836

837
838
839
840
841
842
		/* 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);
843

844
845
846
		/* Renumber starting with the beginning line of the old
		 * partition. */
		renumber(top_save);
847

848
		/* Restore the old edittop. */
849
		openfile->edittop = edittop_save;
850

851
852
853
		/* Restore the old place we want. */
		openfile->placewewant = pww_save;

854
855
		/* Mark the file as modified. */
		set_modified();
856

857
858
		/* Update the screen. */
		edit_refresh();
859
	    }
860

861
862
863
864
865
	    break;
	}
    }

    free(ans);
866
867
}

868
void do_insertfile_void(void)
869
{
870
871
872
873
874
875
876
877
878
879
#ifdef ENABLE_MULTIBUFFER
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
	statusbar(_("Key illegal in non-multibuffer mode"));
    else
#endif
	do_insertfile(
#ifndef NANO_SMALL
		FALSE
#endif
		);
880
881
882
883

    display_main_list();
}

884
/* When passed "[relative path]" or "[relative path][filename]" in
885
 * origpath, return "[full path]" or "[full path][filename]" on success,
886
887
888
889
 * 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. */
890
char *get_full_path(const char *origpath)
891
{
892
    char *d_here, *d_there = NULL;
893

894
895
    if (origpath == NULL)
    	return NULL;
896

897
898
899
    /* Get the current directory. */
    d_here = charalloc(PATH_MAX + 1);
    d_here = getcwd(d_here, PATH_MAX + 1);
900

901
    if (d_here != NULL) {
902
903
904
905
	const char *last_slash;
	char *d_there_file = NULL;
	bool path_only;
	struct stat fileinfo;
906

907
908
	align(&d_here);

909
910
	/* If the current directory isn't "/", tack a slash onto the end
	 * of it. */
911
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
912
	    d_here = charealloc(d_here, strlen(d_here) + 2);
913
914
	    strcat(d_here, "/");
	}
915

916
917
918
919
920
921
922
923
924
925
926
927
	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. */
928
	if (path_only) {
929
930
931
932
	    size_t d_there_len = strlen(d_there);

	    if (d_there[d_there_len - 1] != '/') {
		d_there = charealloc(d_there, d_there_len + 2);
933
934
935
936
		strcat(d_there, "/");
	    }
	}

937
	/* Search for the last slash in d_there. */
938
939
	last_slash = strrchr(d_there, '/');

940
941
942
943
	/* 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);
944

945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
	    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) {
960
		free(d_there);
961
962
963
964
		d_there = NULL;
	    } else {
		/* Get the full path and save it in d_there. */
		free(d_there);
965

966
967
968
		d_there = charalloc(PATH_MAX + 1);
		d_there = getcwd(d_there, PATH_MAX + 1);

969
970
971
972
973
974
975
976
977
978
		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
979
980
981
982
983
984
985
986
		    /* 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);
987
988
	    }

989
990
	    /* Free d_here, since we're done using it. */
	    free(d_here);
991
	}
992

993
994
995
996
997
	/* 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. */
998
	if (!path_only && d_there != NULL) {
999
1000
1001
1002
1003
1004
	    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. */
1005
1006
1007
	free(d_there_file);
    }

1008
    return d_there;
1009
}
1010

1011
1012
1013
/* 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. */
1014
char *check_writable_directory(const char *path)
1015
{
1016
1017
    char *full_path = get_full_path(path);

1018
    /* If get_full_path() fails, return NULL. */
1019
    if (full_path == NULL)
1020
	return NULL;
1021

1022
1023
1024
    /* 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
1025
	full_path[strlen(full_path) - 1] != '/') {
1026
	free(full_path);
1027
	return NULL;
1028
    }
1029

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1030
    /* Otherwise, return the full path. */
1031
1032
1033
    return full_path;
}

1034
1035
/* This function calls mkstemp(($TMPDIR|P_tmpdir|/tmp/)"nano.XXXXXX").
 * On success, it returns the malloc()ed filename and corresponding FILE
1036
 * stream, opened in "r+b" mode.  On error, it returns NULL for the
1037
1038
 * filename and leaves the FILE stream unchanged. */
char *safe_tempfile(FILE **f)
1039
{
1040
    char *full_tempdir = NULL;
1041
1042
1043
    const char *tmpdir_env;
    int fd;
    mode_t original_umask = 0;
1044

1045
1046
    assert(f != NULL);

1047
1048
1049
    /* If $TMPDIR is set and non-empty, set tempdir to it, run it
     * through get_full_path(), and save the result in full_tempdir.
     * Otherwise, leave full_tempdir set to NULL. */
1050
1051
1052
    tmpdir_env = getenv("TMPDIR");
    if (tmpdir_env != NULL && tmpdir_env[0] != '\0')
	full_tempdir = check_writable_directory(tmpdir_env);
1053

1054
1055
    /* If $TMPDIR is unset, empty, or not a writable directory, and
     * full_tempdir is NULL, try P_tmpdir instead. */
1056
    if (full_tempdir == NULL)
1057
	full_tempdir = check_writable_directory(P_tmpdir);
1058

1059
1060
1061
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1062

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1063
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1064
    strcat(full_tempdir, "nano.XXXXXX");
1065

1066
1067
1068
1069
1070
1071
1072
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

    if (fd != -1)
	*f = fdopen(fd, "r+b");
1073
1074
1075
    else {
	free(full_tempdir);
	full_tempdir = NULL;
1076
    }
1077

1078
1079
    umask(original_umask);

1080
    return full_tempdir;
1081
}
1082
1083

#ifndef DISABLE_OPERATINGDIR
1084
1085
1086
1087
1088
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1089
    if (operating_dir == NULL)
1090
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1091

1092
1093
1094
    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
1095
     * inaccessible, unset operating_dir. */
1096
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1097
1098
1099
1100
1101
1102
1103
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1104
1105
1106
1107
1108
/* 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)
1109
{
1110
1111
1112
1113
    /* 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. */
1114

1115
    char *fullpath;
1116
    bool retval = FALSE;
1117
    const char *whereami1, *whereami2 = NULL;
1118

1119
    /* If no operating directory is set, don't bother doing anything. */
1120
    if (operating_dir == NULL)
1121
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1122

1123
    assert(full_operating_dir != NULL);
1124
1125

    fullpath = get_full_path(currpath);
1126
1127

    /* fullpath == NULL means some directory in the path doesn't exist
1128
     * or is unreadable.  If allow_tabcomp is FALSE, then currpath is
1129
1130
     * what the user typed somewhere.  We don't want to report a
     * non-existent directory as being outside the operating directory,
1131
     * so we return FALSE.  If allow_tabcomp is TRUE, then currpath
1132
1133
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1134
    if (fullpath == NULL)
1135
	return allow_tabcomp;
1136
1137
1138
1139
1140

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

1141
    /* If both searches failed, we're outside the operating directory.
1142
     * Otherwise, check the search results.  If the full operating
1143
1144
1145
     * 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. */
1146
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1147
1148
	retval = TRUE;
    free(fullpath);
1149
1150

    /* Otherwise, we're still inside it. */
1151
    return retval;
1152
}
1153
1154
#endif

1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
#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

1179
/* Read from inn, write to out.  We assume inn is opened for reading,
1180
1181
 * and out for writing.  We return 0 on success, -1 on read error, or -2
 * on write error. */
1182
1183
int copy_file(FILE *inn, FILE *out)
{
1184
    char buf[BUFSIZ];
1185
1186
1187
1188
    size_t charsread;
    int retval = 0;

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

1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
    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
1201

1202
1203
1204
1205
    if (fclose(inn) == EOF)
	retval = -1;
    if (fclose(out) == EOF)
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1206

1207
1208
1209
    return retval;
}

1210
1211
1212
/* 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
1213
1214
 * 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
1215
 *
1216
1217
 * 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.
1218
 *
1219
1220
 * append == APPEND means we are appending instead of overwriting.
 * append == PREPEND means we are prepending instead of overwriting.
1221
 *
1222
1223
 * nonamechange means don't change the current filename.  It is ignored
 * if tmp is FALSE or if we're appending/prepending.
1224
 *
1225
 * Return 0 on success or -1 on error. */
1226
1227
int write_file(const char *name, FILE *f_open, bool tmp, append_type
	append, bool nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1228
{
1229
1230
    int retval = -1;
	/* Instead of returning in this function, you should always
1231
1232
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
1233
    const filestruct *fileptr = openfile->fileage;
1234
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1235
	/* The file descriptor we use. */
1236
    mode_t original_umask = 0;
1237
	/* Our umask, from when nano started. */
1238
    bool realexists;
1239
	/* The result of stat().  TRUE if the file exists, FALSE
1240
	 * otherwise.  If name is a link that points nowhere, realexists
1241
	 * is FALSE. */
1242
1243
    struct stat st;
	/* The status fields filled in by stat(). */
1244
    bool anyexists;
1245
1246
	/* The result of lstat().  The same as realexists, unless name
	 * is a link. */
1247
1248
1249
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1250
	/* name after tilde expansion. */
1251
    FILE *f = NULL;
1252
1253
1254
	/* 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
1255

1256
    assert(name != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1257

1258
    if (name[0] == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1259
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1260

1261
1262
1263
    if (f_open != NULL)
	f = f_open;

1264
1265
    if (!tmp)
	titlebar(NULL);
1266

1267
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1268

1269
#ifndef DISABLE_OPERATINGDIR
1270
    /* If we're writing a temporary file, we're probably going outside
1271
     * the operating directory, so skip the operating directory test. */
1272
    if (!tmp && check_operating_dir(realname, FALSE)) {
1273
1274
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1275
1276
1277
    }
#endif

1278
    anyexists = (lstat(realname, &lst) != -1);
1279

1280
1281
    /* If the temp file exists and isn't already open, give up. */
    if (tmp && anyexists && f_open == NULL)
1282
	goto cleanup_and_exit;
1283

1284
1285
1286
    /* 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)) {
1287
1288
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1289
1290
1291
	goto cleanup_and_exit;
    }

1292
    /* Save the state of file at the end of the symlink (if there is
1293
     * one). */
1294
    realexists = (stat(realname, &st) != -1);
1295

1296
1297
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1298
1299
1300
1301
     * 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. */
1302
1303
1304
    if (ISSET(BACKUP_FILE) && !tmp && realexists && ((append !=
	OVERWRITE || openfile->mark_set) ||
	openfile->current_stat->st_mtime == st.st_mtime)) {
1305
	FILE *backup_file;
1306
	char *backupname;
1307
	struct utimbuf filetime;
1308
	int copy_status;
1309

1310
	/* Save the original file's access and modification times. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1311
1312
	filetime.actime = openfile->current_stat->st_atime;
	filetime.modtime = openfile->current_stat->st_mtime;
1313

1314
1315
1316
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1317

1318
1319
1320
1321
1322
	    if (f == NULL) {
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
		goto cleanup_and_exit;
	    }
1323
1324
	}

1325
	/* If backup_dir is set, we set backupname to
1326
1327
1328
1329
	 * 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]. */
1330
	if (backup_dir != NULL) {
1331
	    char *backuptemp = get_full_path(realname);
1332

1333
	    if (backuptemp == NULL)
1334
1335
1336
1337
1338
1339
		/* 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~. */
1340
		backuptemp = mallocstrcpy(NULL, tail(realname));
1341
	    else {
1342
1343
		size_t i = 0;

1344
1345
1346
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1347
1348
1349
1350
		}
	    }

	    backupname = charalloc(strlen(backup_dir) +
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
		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);
		fclose(f);
		goto cleanup_and_exit;
	    } else {
		free(backupname);
		backupname = backuptemp;
	    }
1366
1367
1368
1369
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1370

1371
1372
1373
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1374
	backup_file = fopen(backupname, "wb");
1375

1376
	if (backup_file == NULL || chmod(backupname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1377
		openfile->current_stat->st_mode) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1378
1379
	    statusbar(_("Error writing %s: %s"), backupname,
		strerror(errno));
1380
	    free(backupname);
1381
1382
1383
1384
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1385
1386
1387
	}

#ifdef DEBUG
1388
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1389
1390
#endif

1391
1392
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1393

1394
	/* And set metadata. */
1395
	if (copy_status != 0 || chown(backupname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1396
1397
		openfile->current_stat->st_uid,
		openfile->current_stat->st_gid) == -1 ||
1398
		utime(backupname, &filetime) == -1) {
1399
1400
	    free(backupname);
	    if (copy_status == -1)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1401
1402
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1403
1404
1405
1406
1407
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1408

1409
1410
	free(backupname);
    }
1411
#endif /* !NANO_SMALL */
1412

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1413
1414
    /* 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
1415
1416
1417
1418
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1419
	goto cleanup_and_exit;
1420
    }
1421

1422
1423
    if (f_open == NULL) {
	original_umask = umask(0);
1424

1425
	/* If we create a temp file, we don't let anyone else access it.
1426
1427
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1428
	    umask(S_IRWXG | S_IRWXO);
1429
1430
	else
	    umask(original_umask);
1431
    }
1432

1433
    /* If we're prepending, copy the file to a temp file. */
1434
    if (append == PREPEND) {
1435
1436
1437
	int fd_source;
	FILE *f_source = NULL;

1438
	tempname = safe_tempfile(&f);
1439

1440
1441
	if (tempname == NULL) {
	    statusbar(_("Prepending to %s failed: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1442
		strerror(errno));
1443
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1444
	}
1445

1446
1447
	if (f_open == NULL) {
	    fd_source = open(realname, O_RDONLY | O_CREAT);
1448

1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
	    if (fd_source != -1) {
		f_source = fdopen(fd_source, "rb");
		if (f_source == NULL) {
		    statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
		    close(fd_source);
		    fclose(f);
		    unlink(tempname);
		    goto cleanup_and_exit;
		}
	    }
1460
1461
1462
	}

	if (copy_file(f_source, f) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1463
1464
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1465
	    unlink(tempname);
1466
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1467
1468
1469
	}
    }

1470
1471
1472
1473
    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*. */
	fd = open(realname, O_WRONLY | O_CREAT |
1474
1475
1476
		((append == APPEND) ? O_APPEND : (tmp ? O_EXCL :
		O_TRUNC)), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
		S_IROTH | S_IWOTH);
1477

1478
1479
	/* Set the umask back to the user's original value. */
	umask(original_umask);
1480

1481
1482
1483
1484
	/* If we couldn't open the file, give up. */
	if (fd == -1) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1485

1486
1487
1488
1489
1490
	    /* tempname has been set only if we're prepending. */
	    if (tempname != NULL)
		unlink(tempname);
	    goto cleanup_and_exit;
	}
1491

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

1494
1495
1496
1497
1498
1499
	if (f == NULL) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
	    close(fd);
	    goto cleanup_and_exit;
	}
1500
1501
    }

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

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

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

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

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

1517
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1518
1519
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1520
	    fclose(f);
1521
	    goto cleanup_and_exit;
1522
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1523

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1524
	/* If we're on the last line of the file, don't write a newline
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1525
1526
1527
1528
1529
1530
1531
	 * 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 {
1532
#ifndef NANO_SMALL
1533
1534
1535
1536
	    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
1537
			strerror(errno));
1538
1539
1540
		    fclose(f);
		    goto cleanup_and_exit;
		}
1541
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1542

1543
	    if (openfile->fmt != MAC_FILE) {
1544
#endif
1545
1546
		if (putc('\n', f) == EOF) {
		    statusbar(_("Error writing %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1547
			strerror(errno));
1548
1549
1550
		    fclose(f);
		    goto cleanup_and_exit;
		}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1551
#ifndef NANO_SMALL
1552
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1553
#endif
1554
	}
Chris Allegretta's avatar
Chris Allegretta committed
1555
1556
1557
1558
1559

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

1560
    /* If we're prepending, open the temp file, and append it to f. */
1561
    if (append == PREPEND) {
1562
1563
1564
1565
	int fd_source;
	FILE *f_source = NULL;

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

1567
1568
1569
1570
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1571
	}
1572

1573
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1574
1575
	    statusbar(_("Error reading %s: %s"), tempname,
		strerror(errno));
1576
	    fclose(f);
1577
	    goto cleanup_and_exit;
1578
1579
	}

1580
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1581
1582
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1583
	    goto cleanup_and_exit;
1584
	}
1585
1586
1587
1588
    } 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
1589
    }
1590

1591
    if (!tmp && append == OVERWRITE) {
Chris Allegretta's avatar
Chris Allegretta committed
1592
	if (!nonamechange) {
1593
1594
	    openfile->filename = mallocstrcpy(openfile->filename,
		realname);
Chris Allegretta's avatar
Chris Allegretta committed
1595
#ifdef ENABLE_COLOR
1596
	    /* We might have changed the filename, so update the colors
1597
1598
	     * to account for it, and then make sure we're using
	     * them. */
1599
	    color_update();
1600
	    color_init();
1601
1602
1603
1604
1605

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

1610
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1611
1612
1613
1614
1615
	/* 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);
1616
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1617

1618
1619
	statusbar(P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten),
1620
		(unsigned long)lineswritten);
1621
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1622
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1623
    }
1624

1625
    retval = 0;
1626
1627
1628

  cleanup_and_exit:
    free(realname);
1629
    free(tempname);
1630

1631
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1632
1633
}

1634
#ifndef NANO_SMALL
1635
/* Write a marked selection from a file out. */
1636
1637
int write_marked_file(const char *name, FILE *f_open, bool tmp,
	append_type append)
1638
1639
{
    int retval = -1;
1640
1641
    bool old_modified = openfile->modified;
	/* write_file() unsets the modified flag. */
1642
    bool added_magicline = FALSE;
1643
1644
1645
1646
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

1647
1648
    assert(openfile->mark_set);

1649
1650
1651
    /* Partition the filestruct so that it contains only the marked
     * text. */
    mark_order((const filestruct **)&top, &top_x,
1652
	(const filestruct **)&bot, &bot_x, NULL);
1653
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1654

1655
1656
1657
1658
1659
1660
    /* 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')))
1661
	new_magicline();
1662

1663
    retval = write_file(name, f_open, tmp, append, TRUE);
1664

1665
1666
1667
    /* If the NO_NEWLINES flag isn't set, and we added a magicline,
     * remove it now. */
    if (!ISSET(NO_NEWLINES) && added_magicline)
1668
1669
1670
1671
	remove_magicline();

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

1674
    if (old_modified)
1675
1676
1677
1678
1679
1680
	set_modified();

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

1681
int do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1682
{
1683
1684
    int i, retval = 0;
    append_type append = OVERWRITE;
1685
1686
    char *ans;
	/* The last answer the user typed on the statusbar. */
1687
#ifdef NANO_EXTRA
1688
    static bool did_credits = FALSE;
1689
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1690

1691
    currshortcut = writefile_list;
1692

1693
    if (exiting && openfile->filename[0] != '\0' && ISSET(TEMP_FILE)) {
1694
	retval = write_file(openfile->filename, NULL, FALSE, OVERWRITE,
1695
		FALSE);
1696
1697

	/* Write succeeded. */
1698
	if (retval == 0)
1699
	    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1700
1701
    }

1702
    ans = mallocstrcpy(NULL,
1703
#ifndef NANO_SMALL
1704
	(openfile->mark_set && !exiting) ? "" :
1705
#endif
1706
	openfile->filename);
1707
1708
1709
1710

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

1713
1714
1715
1716
1717
	formatstr = (openfile->fmt == DOS_FILE) ?
		N_(" [DOS Format]") : (openfile->fmt == MAC_FILE) ?
		N_(" [Mac Format]") : "";

	backupstr = ISSET(BACKUP_FILE) ? N_(" [Backup]") : "";
1718

1719
	if (openfile->mark_set && !exiting)
1720
1721
1722
	    msg = (append == PREPEND) ?
		N_("Prepend Selection to File") : (append == APPEND) ?
		N_("Append Selection to File") :
1723
		N_("Write Selection to File");
1724
	else
1725
#endif /* !NANO_SMALL */
1726
1727
	    msg = (append == PREPEND) ? N_("File Name to Prepend to") :
		(append == APPEND) ? N_("File Name to Append to") :
1728
		N_("File Name to Write");
1729

1730
1731
1732
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
1733
	i = do_prompt(!ISSET(RESTRICTED) ||
1734
		openfile->filename[0] == '\0', writefile_list, ans,
1735
#ifndef NANO_SMALL
1736
		NULL, "%s%s%s", _(msg), formatstr, backupstr
1737
#else
1738
		"%s", _(msg)
1739
1740
1741
#endif
		);

1742
	if (i < 0) {
1743
	    statusbar(_("Cancelled"));
1744
1745
1746
1747
	    retval = -1;
	    break;
	} else {
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
1748

1749
#ifndef DISABLE_BROWSER
1750
1751
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
1752

1753
1754
1755
1756
		currshortcut = writefile_list;

		if (tmp == NULL)
		    continue;
1757

1758
1759
1760
1761
1762
		free(answer);
		answer = tmp;

		/* We have a file now.  Get out of the statusbar prompt
		 * cleanly. */
1763
		do_prompt_abort();
1764
	    } else
1765
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1766
#ifndef NANO_SMALL
1767
	    if (i == TOGGLE_DOS_KEY) {
1768
1769
		openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE :
			DOS_FILE;
1770
1771
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1772
1773
		openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE :
			MAC_FILE;
1774
1775
1776
1777
1778
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1779
#endif /* !NANO_SMALL */
1780
	    if (i == NANO_PREPEND_KEY) {
1781
		append = (append == PREPEND) ? OVERWRITE : PREPEND;
1782
1783
		continue;
	    } else if (i == NANO_APPEND_KEY) {
1784
		append = (append == APPEND) ? OVERWRITE : APPEND;
1785
1786
		continue;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1787

Chris Allegretta's avatar
Chris Allegretta committed
1788
#ifdef DEBUG
1789
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1790
#endif
1791
1792

#ifdef NANO_EXTRA
1793
	    if (exiting && !ISSET(TEMP_FILE) &&
1794
		strcasecmp(answer, "zzy") == 0 && !did_credits) {
1795
		do_credits();
1796
		did_credits = TRUE;
1797
1798
1799
		retval = -1;
		break;
	    }
1800
#endif
1801
	    if (append == OVERWRITE && strcmp(answer,
1802
		openfile->filename) != 0) {
1803
1804
1805
		struct stat st;

		if (!stat(answer, &st)) {
1806
1807
		    i = do_yesno_prompt(FALSE,
			_("File exists, OVERWRITE ? "));
1808
1809
1810
		    if (i == 0 || i == -1)
			continue;
		/* If we're using restricted mode, we aren't allowed to
1811
		 * change the name of a file once it has one, because
1812
1813
1814
		 * 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. */
1815
1816
		} else if (!ISSET(RESTRICTED) &&
			openfile->filename[0] != '\0'
1817
#ifndef NANO_SMALL
1818
			&& (exiting || !openfile->mark_set)
1819
#endif
1820
			) {
1821
		    i = do_yesno_prompt(FALSE,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1822
			_("Save file under DIFFERENT NAME ? "));
1823
1824
1825
		    if (i == 0 || i == -1)
			continue;
		}
1826
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1827

1828
#ifndef NANO_SMALL
1829
1830
1831
1832
	    /* 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. */
1833
	    if (!ISSET(RESTRICTED) && !exiting && openfile->mark_set)
1834
		retval = write_marked_file(answer, NULL, FALSE, append);
1835
	    else
1836
#endif /* !NANO_SMALL */
1837
		retval = write_file(answer, NULL, FALSE, append, FALSE);
1838

1839
1840
	    break;
	}
1841
    } /* while (TRUE) */
1842
1843

    free(ans);
1844

1845
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1846
1847
}

1848
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1849
{
1850
    do_writeout(FALSE);
1851
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1852
}
Chris Allegretta's avatar
Chris Allegretta committed
1853

1854
1855
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
1856
char *real_dir_from_tilde(const char *buf)
1857
{
Chris Allegretta's avatar
Chris Allegretta committed
1858
    char *dirtmp = NULL;
1859

1860
    assert(buf != NULL);
1861

1862
    if (buf[0] == '~') {
1863
	size_t i;
1864
	const char *tilde_dir;
1865

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

1870
1871
1872
1873
1874
1875
1876
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
	    tilde_dir = homedir;
	} else {
	    const struct passwd *userdata;

1877
1878
1879
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1880
		strncmp(userdata->pw_name, buf + 1, i - 1) != 0);
1881
1882
	    endpwent();
	    tilde_dir = userdata->pw_dir;
Chris Allegretta's avatar
Chris Allegretta committed
1883
	}
1884

1885
1886
1887
	if (tilde_dir != NULL) {
	    dirtmp = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	    sprintf(dirtmp, "%s%s", tilde_dir, buf + i);
1888
	}
1889
    }
1890

1891
1892
    /* Set a default value for dirtmp, in case the user's home directory
     * isn't found. */
1893
    if (dirtmp == NULL)
1894
	dirtmp = mallocstrcpy(NULL, buf);
1895

1896
    return dirtmp;
1897
1898
}

1899
#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
1900
/* Our sort routine for file listings.  Sort alphabetically and
1901
 * case-insensitively, and sort directories before filenames. */
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
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;

1915
    return mbstrcasecmp(a, b);
1916
}
1917
1918
1919
1920
1921
1922
1923
1924
1925

/* Free the memory allocated for array, which should contain len
 * elements. */
void free_chararray(char **array, size_t len)
{
    for (; len > 0; len--)
	free(array[len - 1]);
    free(array);
}
1926
1927
#endif

Chris Allegretta's avatar
Chris Allegretta committed
1928
#ifndef DISABLE_TABCOMP
1929
1930
/* Is the given file a directory? */
int is_dir(const char *buf)
1931
{
1932
    char *dirptr = real_dir_from_tilde(buf);
1933
1934
    struct stat fileinfo;

1935
    int ret = (stat(dirptr, &fileinfo) != -1 &&
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1936
	S_ISDIR(fileinfo.st_mode));
1937

1938
    assert(buf != NULL && dirptr != buf);
1939

1940
    free(dirptr);
1941

1942
    return ret;
1943
}
Chris Allegretta's avatar
Chris Allegretta committed
1944

1945
/* These functions (username_tab_completion(), cwd_tab_completion(), and
Chris Allegretta's avatar
Chris Allegretta committed
1946
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1947
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
 *
 * 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.
1960
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
1961

1962
1963
1964
1965
/* 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
1966
{
1967
1968
    char **matches = NULL;
    const struct passwd *userdata;
1969

1970
    assert(buf != NULL && num_matches != NULL && buflen > 0);
1971

1972
    *num_matches = 0;
1973

1974
    while ((userdata = getpwent()) != NULL) {
1975
1976
1977
	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... */
1978

1979
1980
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
1981
1982
1983
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
1984
1985
#endif

1986
1987
	    matches = (char **)nrealloc(matches,
		(*num_matches + 1) * sizeof(char *));
1988
1989
1990
	    matches[*num_matches] =
		charalloc(strlen(userdata->pw_name) + 2);
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
1991
	    ++(*num_matches);
1992
	}
1993
1994
    }
    endpwent();
1995

1996
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
1997
1998
}

1999
/* This was originally called exe_n_cwd_tab_completion(), but we're not
2000
2001
2002
 * worried about executables, only filenames :> */
char **cwd_tab_completion(const char *buf, size_t *num_matches, size_t
	buflen)
Chris Allegretta's avatar
Chris Allegretta committed
2003
{
2004
    char *dirname = mallocstrcpy(NULL, buf), *filename;
2005
2006
2007
2008
2009
#ifndef DISABLE_OPERATINGDIR
    size_t dirnamelen;
#endif
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2010
    DIR *dir;
2011
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2012

2013
    assert(dirname != NULL && num_matches != NULL && buflen >= 0);
2014

2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
    *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
2025
	*tmpdirname = '\0';
2026
2027
2028
	tmpdirname = dirname;
	dirname = real_dir_from_tilde(dirname);
	free(tmpdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2029
    } else {
2030
2031
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2032
2033
    }

2034
    assert(dirname[strlen(dirname) - 1] == '/');
2035

Chris Allegretta's avatar
Chris Allegretta committed
2036
    dir = opendir(dirname);
2037

2038
    if (dir == NULL) {
2039
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2040
	beep();
2041
2042
2043
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2044
    }
2045
2046
2047
2048
2049
2050

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

2051
    while ((nextdir = readdir(dir)) != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2052
#ifdef DEBUG
2053
	fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2054
#endif
2055
	/* See if this matches. */
2056
2057
2058
2059
	if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
		(*filename == '.' ||
		(strcmp(nextdir->d_name, ".") != 0 &&
		strcmp(nextdir->d_name, "..") != 0))) {
2060
2061
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2062
2063
2064

#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2065
2066
2067
2068
2069
	     * directory, in which case just go to the next match.  To
	     * properly do operating directory checking, we have to add
	     * the directory name to the beginning of the proposed match
	     * before we check it. */
	    char *tmp2 = charalloc(strlen(dirname) +
2070
		strlen(nextdir->d_name) + 1);
2071

2072
	    sprintf(tmp2, "%s%s", dirname, nextdir->d_name);
2073
2074
2075
	    if (check_operating_dir(tmp2, TRUE)) {
		free(tmp2);
		continue;
2076
	    }
2077
	    free(tmp2);
2078
2079
#endif

2080
2081
	    matches = (char **)nrealloc(matches,
		(*num_matches + 1) * sizeof(char *));
2082
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2083
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2084
2085
	}
    }
2086

2087
2088
    closedir(dir);
    free(dirname);
2089
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2090

2091
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2092
2093
}

2094
2095
/* Do tab completion.  place refers to how much the statusbar cursor
 * position should be advanced. */
2096
char *input_tab(char *buf, size_t *place, bool *lastwastab, bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2097
{
2098
2099
    size_t num_matches = 0;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2100

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2103
    *list = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2104

2105
2106
2107
2108
    /* 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
2109

2110
2111
2112
2113
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2114

2115
2116
2117
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
	matches = cwd_tab_completion(buf, &num_matches, *place);
2118

2119
2120
2121
2122
2123
    if (num_matches <= 0)
	beep();
    else {
	size_t match, common_len = 0;
	char *mzero;
2124
2125
2126
	const char *lastslash = revstrstr(buf, "/", buf + *place);
	size_t lastslash_len = (lastslash == NULL) ? 0 :
		lastslash - buf + 1;
2127
2128
2129
	char *match1_mb = charalloc(mb_cur_max() + 1);
	char *match2_mb = charalloc(mb_cur_max() + 1);
	int match1_mb_len, match2_mb_len;
2130
2131
2132

	while (TRUE) {
	    for (match = 1; match < num_matches; match++) {
2133
2134
		/* Get the number of single-byte characters that all the
		 * matches have in common. */
2135
		match1_mb_len = parse_mbchar(matches[0] + common_len,
2136
			match1_mb, NULL);
2137
		match2_mb_len = parse_mbchar(matches[match] +
2138
			common_len, match2_mb, NULL);
2139
2140
2141
		match1_mb[match1_mb_len] = '\0';
		match2_mb[match2_mb_len] = '\0';
		if (strcmp(match1_mb, match2_mb) != 0)
2142
2143
		    break;
	    }
2144

2145
	    if (match < num_matches || matches[0][common_len] == '\0')
2146
		break;
2147

2148
	    common_len += parse_mbchar(buf + common_len, NULL, NULL);
2149
	}
2150

2151
2152
2153
	free(match1_mb);
	free(match2_mb);

2154
2155
	mzero = charalloc(lastslash_len + common_len + 1);
	sprintf(mzero, "%.*s%.*s", lastslash_len, buf, common_len,
2156
		matches[0]);
2157

2158
	common_len += lastslash_len;
2159

2160
	assert(common_len >= *place);
2161

2162
2163
2164
	if (num_matches == 1 && is_dir(mzero)) {
	    mzero[common_len] = '/';
	    common_len++;
2165

2166
2167
	    assert(common_len > *place);
	}
2168

2169
2170
2171
	if (num_matches > 1 && (common_len != *place ||
		*lastwastab == FALSE))
	    beep();
2172

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2173
	/* If there is more of a match to display on the statusbar, show
2174
	 * it.  We reset lastwastab to FALSE: it requires pressing Tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2175
	 * twice in succession with no statusbar changes to see a match
2176
2177
	 * list. */
	if (common_len != *place) {
2178
	    size_t buf_len = strlen(buf);
2179
2180

	    *lastwastab = FALSE;
2181
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2182
2183
	    charmove(buf + common_len, buf + *place, buf_len -
		*place + 1);
2184
	    strncpy(buf, mzero, common_len);
2185
	    *place = common_len;
2186
2187
2188
	} else if (*lastwastab == FALSE || num_matches < 2)
	    *lastwastab = TRUE;
	else {
2189
	    int longest_name = 0, columns, editline = 0;
2190

2191
2192
2193
2194
2195
2196
2197
2198
	    /* 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
2199

2200
2201
		if (common_len > COLS - 1) {
		    longest_name = COLS - 1;
2202
2203
		    break;
		}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2204

2205
2206
		if (common_len > longest_name)
		    longest_name = common_len;
2207
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2208

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

2211
2212
2213
	    /* 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. */
2214
	    columns = (COLS + 1) / (longest_name + 2);
2215

2216
2217
2218
2219
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2220

2221
2222
	    /* Disable el cursor. */
	    curs_set(0);
2223

2224
2225
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2226

2227
2228
		wmove(edit, editline, (longest_name + 2) *
			(match % columns));
2229

2230
2231
2232
		if (match % columns == 0 &&
			editline == editwinrows - 1 &&
			num_matches - match > columns) {
2233
2234
2235
		    waddstr(edit, _("(more)"));
		    break;
		}
2236

2237
2238
2239
2240
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2241

2242
		if ((match + 1) % columns == 0)
2243
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2244
	    }
2245

2246
	    wnoutrefresh(edit);
2247
	    *list = TRUE;
2248
2249
2250
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2251
2252
    }

2253
    free_chararray(matches, num_matches);
2254

2255
    /* Only refresh the edit window if we don't have a list of filename
2256
     * matches on it. */
2257
    if (*list == FALSE)
2258
	edit_refresh();
2259
2260

    /* Enable el cursor. */
2261
    curs_set(1);
2262

2263
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2264
}
2265
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2266

2267
2268
/* Only print the last part of a path.  Isn't there a shell command for
 * this? */
2269
2270
const char *tail(const char *foo)
{
2271
    const char *tmp = strrchr(foo, '/');
2272

2273
2274
2275
    if (tmp == NULL)
	tmp = foo;
    else if (*tmp == '/')
2276
2277
2278
2279
2280
	tmp++;

    return tmp;
}

2281
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2282
2283
2284
/* 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)
2285
{
2286
    char *nanohist = NULL;
2287

2288
2289
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2290

2291
2292
2293
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2294
    }
2295
2296
2297
    return nanohist;
}

2298
/* Load histories from ~/.nano_history. */
2299
2300
2301
void load_history(void)
{
    char *nanohist = histfilename();
Chris Allegretta's avatar
Chris Allegretta committed
2302

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2303
    /* Assume do_rcfile() has reported a missing home directory. */
2304
2305
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2306

2307
	if (hist == NULL) {
2308
	    if (errno != ENOENT) {
2309
2310
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2311
2312
2313
2314
		rcfile_error(N_("Error reading %s: %s"), nanohist,
			strerror(errno));
		fprintf(stderr,
			_("\nPress Return to continue starting nano\n"));
2315
2316
		while (getchar() != '\n')
		    ;
2317
	    }
2318
	} else {
2319
2320
2321
2322
	    /* 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;
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
	    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);
2334
2335
		    update_history(history, line);
		} else
2336
2337
		    history = &replace_history;
	    }
2338

2339
	    fclose(hist);
2340
	    free(line);
2341
	}
2342
2343
2344
2345
	free(nanohist);
    }
}

2346
bool writehist(FILE *hist, filestruct *h)
2347
{
2348
    filestruct *p;
2349

2350
2351
2352
    /* Write history from oldest to newest.  Assume the last history
     * entry is a blank line. */
    for (p = h; p != NULL; p = p->next) {
2353
	size_t p_len = strlen(p->data);
2354

2355
	sunder(p->data);
2356

2357
	if (fwrite(p->data, sizeof(char), p_len, hist) < p_len ||
2358
2359
		putc('\n', hist) == EOF)
	    return FALSE;
2360
    }
2361

2362
    return TRUE;
2363
2364
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2365
/* Save histories to ~/.nano_history. */
2366
2367
void save_history(void)
{
2368
    char *nanohist;
2369

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2370
    /* Don't save unchanged or empty histories. */
2371
2372
    if (!history_has_changed() || (searchbot->lineno == 1 &&
	replacebot->lineno == 1))
2373
2374
	return;

2375
2376
2377
2378
    nanohist = histfilename();

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

2380
	if (hist == NULL)
2381
2382
	    rcfile_error(N_("Error writing %s: %s"), nanohist,
		strerror(errno));
2383
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2384
2385
	    /* Make sure no one else can read from or write to the
	     * history file. */
2386
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2387

2388
2389
	    if (!writehist(hist, searchage) || !writehist(hist,
		replaceage))
2390
2391
		rcfile_error(N_("Error writing %s: %s"), nanohist,
			strerror(errno));
2392

2393
2394
	    fclose(hist);
	}
2395

2396
2397
2398
	free(nanohist);
    }
}
2399
#endif /* !NANO_SMALL && ENABLE_NANORC */