files.c 84.7 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
6
 *   Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,  *
 *   2008, 2009 Free Software Foundation, Inc.                            *
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 3, 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
79
    openfile->undotop = NULL;
    openfile->current_undo = 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
    openfile->filebot = openfile->fileage;
    openfile->edittop = openfile->fileage;
    openfile->current = openfile->fileage;

99
100
101
102
#ifdef ENABLE_COLOR
    openfile->fileage->multidata = NULL;
#endif

103
    openfile->totsize = 0;
104
105
}

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

121
122
    assert(filename != NULL);

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

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

136
137
    /* If we're loading into a new buffer, add a new entry to
     * openfile. */
138
139
    if (new_buffer)
	make_new_buffer();
140

141
142
    /* If we have a file, and we're loading into a new buffer, update
     * the filename. */
143
144
    if (rc != -1 && new_buffer)
	openfile->filename = mallocstrcpy(openfile->filename, filename);
145

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

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

#ifdef ENABLE_COLOR
168
169
    /* If we're loading into a new buffer, update the colors to account
     * for it, if applicable. */
170
171
172
    if (new_buffer)
	color_update();
#endif
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
#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. */
198
    if (rc > 0)
199
	read_file(f, rc, filename, FALSE, TRUE);
200
201
202
203
204
205
206
207

    /* 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 */

208
/* Update the screen to account for the current buffer. */
209
void display_buffer(void)
210
{
211
    /* Update the titlebar, since the filename may have changed. */
212
    titlebar(NULL);
213
214

#ifdef ENABLE_COLOR
215
216
217
    /* Make sure we're using the buffer's associated colors, if
     * applicable. */
    color_init();
218
219
220
#endif

    /* Update the edit window. */
221
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
222
223
}

224
225
226
227
#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
228
{
229
    assert(openfile != NULL);
230

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

238
239
    /* Switch to the next or previous file buffer, depending on the
     * value of next_buf. */
240
    openfile = next_buf ? openfile->next : openfile->prev;
241
242

#ifdef DEBUG
243
    fprintf(stderr, "filename is %s\n", openfile->filename);
244
245
#endif

246
    /* Update the screen to account for the current buffer. */
247
    display_buffer();
248

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

#ifdef DEBUG
255
    dump_filestruct(openfile->current);
256
#endif
Chris Allegretta's avatar
Chris Allegretta committed
257
258
}

259
/* Switch to the previous entry in the openfile filebuffer. */
260
void switch_to_prev_buffer_void(void)
261
{
262
    switch_to_prevnext_buffer(FALSE);
263
}
264

265
/* Switch to the next entry in the openfile filebuffer. */
266
void switch_to_next_buffer_void(void)
267
{
268
    switch_to_prevnext_buffer(TRUE);
269
}
270

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

278
    /* If only one file buffer is open, get out. */
279
    if (openfile == openfile->next)
280
	return FALSE;
281

282
283
284
285
#ifndef NANO_TINY
        update_poshistory(openfile->filename, openfile->current->lineno, xplustabs()+1);
#endif /* NANO_TINY */

286
    /* Switch to the next file buffer. */
287
    switch_to_next_buffer_void();
288

289
    /* Close the file buffer we had open before. */
290
    unlink_opennode(openfile->prev);
291

292
    display_main_list();
293

294
    return TRUE;
295
}
296
#endif /* ENABLE_MULTIBUFFER */
297

298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
/* A bit of a copy and paste from open_file(), is_file_writable()
 * just checks whether the file is appendable as a quick
 * permissions check, and we tend to err on the side of permissiveness
 * (reporting TRUE when it might be wrong) to not fluster users
 * editing on odd filesystems by printing incorrect warnings.
 */
int is_file_writable(const char *filename)
{
    struct stat fileinfo, fileinfo2;
    int fd;
    FILE *f;
    char *full_filename;
    bool ans = TRUE;


    if (ISSET(VIEW_MODE))
	return TRUE;

316
    assert(filename != NULL);
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338

    /* Get the specified file's full path. */
    full_filename = get_full_path(filename);

    /* Okay, if we can't stat the path due to a component's
       permissions, just try the relative one */
    if (full_filename == NULL
        || (stat(full_filename, &fileinfo) == -1 && stat(filename, &fileinfo2) != -1))
        full_filename = mallocstrcpy(NULL, filename);

    if ((fd = open(full_filename, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR |
                S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) == -1
	|| (f = fdopen(fd, "a")) == NULL)
	ans = FALSE;
    else
        fclose(f);
    close(fd);

    free(full_filename);
    return ans;
}

339
340
341
342
343
344
/* 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)
345
{
346
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
347

348
349
    /* Convert nulls to newlines.  buf_len is the string's real
     * length. */
350
    unsunder(buf, buf_len);
351

352
    assert(openfile->fileage != NULL && strlen(buf) == buf_len);
353

354
    fileptr->data = mallocstrcpy(NULL, buf);
355

356
#ifndef NANO_TINY
357
358
359
360
    /* 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';
361
#endif
362

363
#ifdef ENABLE_COLOR
364
	fileptr->multidata = NULL;
365
366
#endif

367
    if (*first_line_ins) {
368
369
370
	/* Special case: We're inserting with the cursor on the first
	 * line. */
	fileptr->prev = NULL;
371
	fileptr->next = openfile->fileage;
372
	fileptr->lineno = 1;
373
	if (*first_line_ins) {
374
375
376
377
	    *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. */
378
	    openfile->edittop = fileptr;
379
	} else
380
381
	    openfile->filebot = fileptr;
	openfile->fileage = fileptr;
382
383
384
385
386
387
388
    } else {
	assert(prevnode != NULL);

	fileptr->prev = prevnode;
	fileptr->next = NULL;
	fileptr->lineno = prevnode->lineno + 1;
	prevnode->next = fileptr;
389
390
    }

391
392
    return fileptr;
}
393

394
/* Read an open file into the current buffer.  f should be set to the
395
 * open file, and filename should be set to the name of the file.
396
 * undoable  means do we want to create undo records to try and undo this.
397
 * Will also attempt to check file writability if fd > 0 and checkwritable == TRUE
398
 */
399
void read_file(FILE *f, int fd, const char *filename, bool undoable, bool checkwritable)
400
401
402
403
404
405
406
407
408
409
410
411
412
{
    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. */
413
    filestruct *fileptr = openfile->current;
414
415
416
417
418
419
	/* 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. */
420
421
    bool writable = TRUE;
	/* Is the file writable (if we care) */
422
#ifndef NANO_TINY
423
424
    int format = 0;
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
425
#endif
426

427
428
    assert(openfile->fileage != NULL && openfile->current != NULL);

429
430
    buf = charalloc(bufx);
    buf[0] = '\0';
431

432
433
434
435
436
#ifndef NANO_TINY
    if (undoable)
	add_undo(INSERT);
#endif

437
438
439
440
    if (openfile->current == openfile->fileage)
	first_line_ins = TRUE;
    else
	fileptr = openfile->current->prev;
441

442
    /* Read the entire file into the filestruct. */
443
444
445
446
447
448
    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') {
449
#ifndef NANO_TINY
450
451
452
453
454
455
456
457
458
	    /* If it's a DOS file or a DOS/Mac file ('\r' before '\n' on
	     * the first line if we think it's a *nix file, or on any
	     * line otherwise), and file conversion isn't disabled,
	     * handle it! */
	    if (!ISSET(NO_CONVERT) && (num_lines == 0 || format != 0) &&
		i > 0 && buf[i - 1] == '\r') {
		if (format == 0 || format == 2)
		    format++;
	    }
459
#endif
460

461
462
	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
463

464
465
466
	    /* Reset the line length in preparation for the next
	     * line. */
	    len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
467

468
469
470
	    num_lines++;
	    buf[0] = '\0';
	    i = 0;
471
#ifndef NANO_TINY
472
473
474
475
476
	/* If it's a Mac file ('\r' without '\n' on the first line if we
	 * think it's a *nix file, or on any line otherwise), and file
	 * conversion isn't disabled, handle it! */
	} else if (!ISSET(NO_CONVERT) && (num_lines == 0 ||
		format != 0) && i > 0 && buf[i - 1] == '\r') {
477
478
479
480
481
	    /* 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;
482

483
484
	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
485

486
487
488
489
	    /* 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
490

491
492
493
494
	    num_lines++;
	    buf[0] = input;
	    buf[1] = '\0';
	    i = 1;
495
#endif
496
497
498
499
	} else {
	    /* Calculate the total length of the line.  It might have
	     * nulls in it, so we can't just use strlen() here. */
	    len++;
500

501
502
503
504
505
506
507
508
	    /* 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);
509
	    }
510
511
512
513
514
515
516
517
518
519
520

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

    /* Perhaps this could use some better handling. */
    if (ferror(f))
	nperror(filename);
    fclose(f);
521
    if (fd > 0 && checkwritable) {
522
	close(fd);
523
524
	writable = is_file_writable(filename);
    }
525

526
#ifndef NANO_TINY
527
528
529
530
531
532
533
534
    /* 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';
    }
535
#endif
536

537
538
    /* Did we not get a newline and still have stuff to do? */
    if (len > 0) {
539
#ifndef NANO_TINY
540
541
542
543
544
545
546
	/* 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;
547
548
#endif

549
550
551
552
	/* Read in the last line properly. */
	fileptr = read_line(buf, fileptr, &first_line_ins, len);
	num_lines++;
    }
553

554
    free(buf);
555

556
    /* If we didn't get a file and we don't already have one, open a
557
     * blank buffer. */
558
    if (fileptr == NULL)
559
	open_buffer("", FALSE);
560

561
562
    /* Attach the file we got to the filestruct.  If we got a file of
     * zero bytes, don't do anything. */
563
    if (num_lines > 0) {
564
565
566
567
568
569
570
571
572
573
574
575
	/* 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
576
577
	    /* Tack the text at fileptr onto the beginning of the text
	     * at current. */
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
	    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
593
	    /* Move fileptr back one line and blow away the old fileptr,
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
	     * 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. */
610
	renumber(openfile->current);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
611
    }
612

613
614
    openfile->totsize += get_totsize(openfile->fileage,
	openfile->filebot);
615

616
    /* If the NO_NEWLINES flag isn't set, and text has been added to
617
     * the magicline (i.e. a file that doesn't end in a newline has been
618
619
620
     * 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') {
621
622
623
624
625
626
627
628
629
	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();

630
#ifndef NANO_TINY
631
632
633
    if (undoable)
	update_undo(INSERT);

634
635
636
    if (format == 3) {
	if (writable)
	    statusbar(
637
638
639
		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);
640
641
642
643
644
645
	else
	    statusbar(
		P_("Read %lu line (Converted from DOS and Mac format - Warning: No write permission)",
		"Read %lu lines (Converted from DOS and Mac format - Warning: No write permission)",
		(unsigned long)num_lines), (unsigned long)num_lines);
    } else if (format == 2) {
646
	openfile->fmt = MAC_FILE;
647
648
	if (writable)
	    statusbar(P_("Read %lu line (Converted from Mac format)",
649
650
		"Read %lu lines (Converted from Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
651
652
653
654
	else
	    statusbar(P_("Read %lu line (Converted from Mac format - Warning: No write permission)",
		"Read %lu lines (Converted from Mac format - Warning: No write permission)",
		(unsigned long)num_lines), (unsigned long)num_lines);
655
    } else if (format == 1) {
656
	openfile->fmt = DOS_FILE;
657
658
	if (writable)
	    statusbar(P_("Read %lu line (Converted from DOS format)",
659
660
		"Read %lu lines (Converted from DOS format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
661
662
663
664
	else
	    statusbar(P_("Read %lu line (Converted from DOS format - Warning: No write permission)",
		"Read %lu lines (Converted from DOS format - Warning: No write permission)",
		(unsigned long)num_lines), (unsigned long)num_lines);
665
    } else
666
#endif
667
668
669
670
671
672
	if (writable)
	    statusbar(P_("Read %lu line", "Read %lu lines",
		(unsigned long)num_lines), (unsigned long)num_lines);
	else
	    statusbar(P_("Read %lu line ( Warning: No write permission)",
		"Read %lu lines (Warning: No write permission)",
673
674
		(unsigned long)num_lines), (unsigned long)num_lines);
}
Chris Allegretta's avatar
Chris Allegretta committed
675

676
677
678
679
/* 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".
 *
680
 * Return -2 if we say "New File", -1 if the file isn't opened, and the
681
 * fd opened otherwise.  The file might still have an error while reading
682
 * with a 0 return value.  *f is set to the opened file. */
683
684
int open_file(const char *filename, bool newfie, FILE **f)
{
685
    struct stat fileinfo, fileinfo2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
686
    int fd;
687
    char *full_filename;
688

689
    assert(filename != NULL && f != NULL);
690

691
692
693
    /* Get the specified file's full path. */
    full_filename = get_full_path(filename);

694
695
    /* Okay, if we can't stat the path due to a component's
       permissions, just try the relative one */
696
    if (full_filename == NULL
697
	|| (stat(full_filename, &fileinfo) == -1 && stat(filename, &fileinfo2) != -1))
698
699
700
	full_filename = mallocstrcpy(NULL, filename);

    if (stat(full_filename, &fileinfo) == -1) {
701
702
703
704
705
706
707
708
	/* Well, maybe we can open the file even if the OS
	   says its not there */
        if ((fd = open(filename, O_RDONLY)) != -1) {
	    statusbar(_("Reading File"));
	    free(full_filename);
	    return 0;
	}

709
710
711
712
713
	if (newfie) {
	    statusbar(_("New File"));
	    return -2;
	}
	statusbar(_("\"%s\" not found"), filename);
714
	beep();
715
716
	return -1;
    } else if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
717
	S_ISBLK(fileinfo.st_mode)) {
718
719
	/* Don't open directories, character files, or block files.
	 * Sorry, /dev/sndstat! */
720
721
	statusbar(S_ISDIR(fileinfo.st_mode) ?
		_("\"%s\" is a directory") :
722
		_("\"%s\" is a device file"), filename);
723
	beep();
724
	return -1;
725
726
727
    } else if ((fd = open(full_filename, O_RDONLY)) == -1) {
	statusbar(_("Error reading %s: %s"), filename,
		strerror(errno));
728
	beep();
729
730
 	return -1;
     } else {
731
	/* The file is A-OK.  Open it. */
732
	*f = fdopen(fd, "rb");
733

734
735
736
	if (*f == NULL) {
	    statusbar(_("Error reading %s: %s"), filename,
		strerror(errno));
737
	    beep();
738
739
740
	    close(fd);
	} else
	    statusbar(_("Reading File"));
741
    }
742

743
744
    free(full_filename);

745
746
747
    return fd;
}

748
749
750
751
752
/* 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)
753
{
754
    static int ulmax_digits = -1;
755
756
757
    unsigned long i = 0;
    char *buf;
    size_t namelen, suffixlen;
758

759
    assert(name != NULL && suffix != NULL);
760

761
762
763
    if (ulmax_digits == -1)
	ulmax_digits = digits(ULONG_MAX);

764
765
    namelen = strlen(name);
    suffixlen = strlen(suffix);
Chris Allegretta's avatar
Chris Allegretta committed
766

767
    buf = charalloc(namelen + suffixlen + ulmax_digits + 2);
768
    sprintf(buf, "%s%s", name, suffix);
769

770
771
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
772

773
774
775
776
	if (stat(buf, &fs) == -1)
	    return buf;
	if (i == ULONG_MAX)
	    break;
777

778
779
780
	i++;
	sprintf(buf + namelen + suffixlen, ".%lu", i);
    }
Chris Allegretta's avatar
Chris Allegretta committed
781

782
783
784
    /* We get here only if there is no possible save file.  Blank out
     * the filename to indicate this. */
    null_at(&buf, 0);
785

786
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
787
788
}

789
790
791
/* 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. */
792
void do_insertfile(
793
#ifndef NANO_TINY
794
795
796
797
798
799
800
801
802
	bool execute
#else
	void
#endif
	)
{
    int i;
    const char *msg;
    char *ans = mallocstrcpy(NULL, "");
803
	/* The last answer the user typed at the statusbar prompt. */
804
    filestruct *edittop_save = openfile->edittop;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
805
    size_t current_x_save = openfile->current_x;
806
    ssize_t current_y_save = openfile->current_y;
807
808
    bool edittop_inside = FALSE, meta_key = FALSE, func_key = FALSE;
    const sc *s;
809
#ifndef NANO_TINY
810
    bool right_side_up = FALSE, single_line = FALSE;
811
#endif
812

813
814
    currmenu = MINSERTFILE;

815
    while (TRUE) {
816
#ifndef NANO_TINY
817
	if (execute) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
818
	    msg =
819
#ifdef ENABLE_MULTIBUFFER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
820
		ISSET(MULTIBUFFER) ?
821
		_("Command to execute in new buffer [from %s] ") :
822
#endif
823
		_("Command to execute [from %s] ");
824
825
	} else {
#endif
826
	    msg =
827
#ifdef ENABLE_MULTIBUFFER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
828
		ISSET(MULTIBUFFER) ?
829
		_("File to insert into new buffer [from %s] ") :
830
#endif
831
		_("File to insert [from %s] ");
832
#ifndef NANO_TINY
833
834
	}
#endif
835

836
	i = do_prompt(TRUE,
837
838
839
#ifndef DISABLE_TABCOMP
		TRUE,
#endif
840
#ifndef NANO_TINY
841
		execute ? MEXTCMD :
842
#endif
843
		MINSERTFILE, ans,
844
		&meta_key, &func_key,
845
#ifndef NANO_TINY
846
		NULL,
847
#endif
848
		edit_refresh, msg,
849
#ifndef DISABLE_OPERATINGDIR
850
851
		operating_dir != NULL && strcmp(operating_dir,
		".") != 0 ? operating_dir :
852
853
#endif
		"./");
854

855
	/* If we're in multibuffer mode and the filename or command is
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
856
	 * blank, open a new buffer instead of canceling.  If the
857
858
	 * filename or command begins with a newline (i.e. an encoded
	 * null), treat it as though it's blank. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
859
	if (i == -1 || ((i == -2 || *answer == '\n')
860
861
862
863
864
865
866
#ifdef ENABLE_MULTIBUFFER
		&& !ISSET(MULTIBUFFER)
#endif
		)) {
	    statusbar(_("Cancelled"));
	    break;
	} else {
867
	    size_t pww_save = openfile->placewewant;
868

869
	    ans = mallocstrcpy(ans, answer);
870

871
872
	    s = get_shortcut(currmenu, &i, &meta_key, &func_key);

873
#ifndef NANO_TINY
874
#ifdef ENABLE_MULTIBUFFER
875

876
	    if (s && s->scfunc == new_buffer_void) {
877
878
879
880
881
		/* Don't allow toggling if we're in view mode. */
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
		continue;
	    } else
882
#endif
883
	    if (s && s->scfunc == ext_cmd_void) {
884
885
886
887
888
889
		execute = !execute;
		continue;
	    }
#ifndef DISABLE_BROWSER
	    else
#endif
890
#endif /* !NANO_TINY */
891

892
#ifndef DISABLE_BROWSER
893
	    if (s && s->scfunc == to_files_void) {
894
		char *tmp = do_browse_from(answer);
895

896
897
		if (tmp == NULL)
		    continue;
898

899
		/* We have a file now.  Indicate this. */
900
901
		free(answer);
		answer = tmp;
902

903
904
905
		i = 0;
	    }
#endif
906

907
908
909
910
911
912
913
914
	    /* 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;
915

916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
#ifndef NANO_TINY
		/* Keep track of whether the mark begins inside the
		 * partition and will need adjustment. */
		if (openfile->mark_set) {
		    filestruct *top, *bot;
		    size_t top_x, bot_x;

		    mark_order((const filestruct **)&top, &top_x,
			(const filestruct **)&bot, &bot_x,
			&right_side_up);

		    single_line = (top == bot);
		}
#endif

931
932
933
934
935
#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
936
937
938
		 * looks like a new buffer, and keep track of whether
		 * the top of the edit window is inside the
		 * partition. */
939
940
941
		filepart = partition_filestruct(openfile->current,
			openfile->current_x, openfile->current,
			openfile->current_x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
942
943
		edittop_inside =
			(openfile->edittop == openfile->fileage);
944
945
946
#ifdef ENABLE_MULTIBUFFER
	    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
947

948
949
	    /* Convert newlines to nulls, just before we insert the file
	     * or execute the command. */
950
	    sunder(answer);
951
	    align(&answer);
952

953
#ifndef NANO_TINY
954
955
956
	    if (execute) {
#ifdef ENABLE_MULTIBUFFER
		if (ISSET(MULTIBUFFER))
957
		    /* Open a blank buffer. */
958
		    open_buffer("", FALSE);
959
960
961
#endif

		/* Save the command's output in the current buffer. */
962
		execute_command(answer);
963
964
965
966
967
968
969
970
971

#ifdef ENABLE_MULTIBUFFER
		if (ISSET(MULTIBUFFER)) {
		    /* Move back to the beginning of the first line of
		     * the buffer. */
		    openfile->current = openfile->fileage;
		    openfile->current_x = 0;
		    openfile->placewewant = 0;
		}
972
#endif
973
974
	    } else {
#endif /* !NANO_TINY */
975
976
		/* Make sure the path to the file specified in answer is
		 * tilde-expanded. */
977
978
		answer = mallocstrassn(answer,
			real_dir_from_tilde(answer));
979
980
981

		/* Save the file specified in answer in the current
		 * buffer. */
982
		open_buffer(answer, TRUE);
983
#ifndef NANO_TINY
984
	    }
Chris Allegretta's avatar
Chris Allegretta committed
985
#endif
986

987
#ifdef ENABLE_MULTIBUFFER
988
989
990
	    if (ISSET(MULTIBUFFER))
		/* Update the screen to account for the current
		 * buffer. */
991
		display_buffer();
992
	    else
Chris Allegretta's avatar
Chris Allegretta committed
993
#endif
994
	    {
995
		filestruct *top_save = openfile->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
996

997
998
999
		/* If we were at the top of the edit window before, set
		 * the saved value of edittop to the new top of the edit
		 * window. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1000
		if (edittop_inside)
1001
		    edittop_save = openfile->fileage;
1002
1003

		/* Update the current x-coordinate to account for the
1004
1005
1006
1007
		 * number of characters inserted on the current line.
		 * If the mark begins inside the partition, adjust the
		 * mark coordinates to compensate for the change in the
		 * current line. */
1008
		openfile->current_x = strlen(openfile->filebot->data);
1009
1010
1011
1012
		if (openfile->fileage == openfile->filebot) {
#ifndef NANO_TINY
		    if (openfile->mark_set) {
			openfile->mark_begin = openfile->current;
1013
			if (!right_side_up)
1014
1015
1016
1017
			    openfile->mark_begin_x +=
				openfile->current_x;
		    }
#endif
1018
		    openfile->current_x += current_x_save;
1019
		}
1020
#ifndef NANO_TINY
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
		else if (openfile->mark_set) {
		    if (!right_side_up) {
			if (single_line) {
			    openfile->mark_begin = openfile->current;
			    openfile->mark_begin_x -= current_x_save;
			} else
			    openfile->mark_begin_x -=
				openfile->current_x;
		    }
		}
1031
#endif
1032
1033
1034

		/* Update the current y-coordinate to account for the
		 * number of lines inserted. */
1035
		openfile->current_y += current_y_save;
1036

1037
1038
1039
1040
		/* 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. */
1041
		unpartition_filestruct(&filepart);
1042

1043
1044
1045
		/* Renumber starting with the beginning line of the old
		 * partition. */
		renumber(top_save);
1046

1047
		/* Restore the old edittop. */
1048
		openfile->edittop = edittop_save;
1049

1050
1051
1052
		/* Restore the old place we want. */
		openfile->placewewant = pww_save;

1053
1054
		/* Mark the file as modified. */
		set_modified();
1055

1056
1057
		/* Update the screen. */
		edit_refresh();
1058
	    }
1059

1060
1061
1062
	    break;
	}
    }
1063
    shortcut_init(FALSE);
1064
1065

    free(ans);
1066
1067
}

1068
1069
1070
/* 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. */
1071
void do_insertfile_void(void)
1072
{
1073
1074
1075
1076
1077
1078

    if (ISSET(RESTRICTED)) {
        nano_disabled_msg();
	return;
    }

1079
1080
#ifdef ENABLE_MULTIBUFFER
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
1081
	statusbar(_("Key invalid in non-multibuffer mode"));
1082
1083
1084
    else
#endif
	do_insertfile(
1085
#ifndef NANO_TINY
1086
1087
1088
		FALSE
#endif
		);
1089
1090
1091
1092

    display_main_list();
}

1093
/* When passed "[relative path]" or "[relative path][filename]" in
1094
 * origpath, return "[full path]" or "[full path][filename]" on success,
1095
1096
1097
1098
 * 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. */
1099
char *get_full_path(const char *origpath)
1100
{
1101
1102
1103
1104
    struct stat fileinfo;
    char *d_here, *d_there, *d_there_file = NULL;
    const char *last_slash;
    bool path_only;
1105

1106
1107
    if (origpath == NULL)
    	return NULL;
1108

1109
    /* Get the current directory.  If it doesn't exist, back up and try
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1110
1111
     * again until we get a directory that does, and use that as the
     * current directory. */
1112
1113
    d_here = charalloc(PATH_MAX + 1);
    d_here = getcwd(d_here, PATH_MAX + 1);
1114

1115
    while (d_here == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1116
1117
1118
	if (chdir("..") == -1)
	    break;

1119
1120
1121
1122
1123
	d_here = getcwd(d_here, PATH_MAX + 1);
    }

    /* If we succeeded, canonicalize it in d_here. */
    if (d_here != NULL) {
1124
1125
	align(&d_here);

1126
1127
	/* If the current directory isn't "/", tack a slash onto the end
	 * of it. */
1128
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1129
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1130
1131
	    strcat(d_here, "/");
	}
1132
1133
1134
    /* Otherwise, set d_here to "". */
    } else
	d_here = mallocstrcpy(NULL, "");
1135

1136
    d_there = real_dir_from_tilde(origpath);
1137

1138
1139
1140
    /* If stat()ing d_there 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 otherwise. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1141
1142
    path_only = (stat(d_there, &fileinfo) != -1 &&
	S_ISDIR(fileinfo.st_mode));
1143

1144
1145
1146
    /* If path_only is TRUE, make sure d_there ends in a slash. */
    if (path_only) {
	size_t d_there_len = strlen(d_there);
1147

1148
1149
1150
	if (d_there[d_there_len - 1] != '/') {
	    d_there = charealloc(d_there, d_there_len + 2);
	    strcat(d_there, "/");
1151
	}
1152
    }
1153

1154
1155
    /* Search for the last slash in d_there. */
    last_slash = strrchr(d_there, '/');
1156

1157
1158
1159
1160
    /* 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);
1161

1162
1163
1164
1165
1166
1167
1168
1169
	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);

1170
	/* Remove the filename portion of the answer from d_there. */
1171
1172
	null_at(&d_there, last_slash - d_there + 1);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1173
	/* Go to the path specified in d_there. */
1174
1175
1176
	if (chdir(d_there) == -1) {
	    free(d_there);
	    d_there = NULL;
1177
	} else {
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
	    free(d_there);

	    /* Get the full path. */
	    d_there = charalloc(PATH_MAX + 1);
	    d_there = getcwd(d_there, PATH_MAX + 1);

	    /* If we succeeded, canonicalize it in d_there. */
	    if (d_there != NULL) {
		align(&d_there);

		/* If the current directory isn't "/", tack a slash onto
		 * the end of it. */
		if (strcmp(d_there, "/") != 0) {
		    d_there = charealloc(d_there, strlen(d_there) + 2);
		    strcat(d_there, "/");
		}
	    } else
		/* Otherwise, 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.  We don't check for a chdir()
1202
	     * error, since we can do nothing if we get one. */
1203
	    IGNORE_CALL_RESULT(chdir(d_here));
1204

1205
1206
	    /* Free d_here, since we're done using it. */
	    free(d_here);
1207
	}
1208
    }
1209

1210
1211
1212
1213
1214
1215
1216
    /* At this point, if path_only is FALSE and d_there isn't NULL,
     * 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 the latter onto the end of the former.  d_there will
     * then contain the complete answer. */
    if (!path_only && d_there != NULL) {
	d_there = charealloc(d_there, strlen(d_there) +
1217
		strlen(d_there_file) + 1);
1218
1219
	strcat(d_there, d_there_file);
    }
1220

1221
1222
    /* Free d_there_file, since we're done using it. */
    if (d_there_file != NULL)
1223
1224
	free(d_there_file);

1225
    return d_there;
1226
}
1227

1228
1229
1230
/* 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. */
1231
char *check_writable_directory(const char *path)
1232
{
1233
1234
    char *full_path = get_full_path(path);

1235
    /* If get_full_path() fails, return NULL. */
1236
    if (full_path == NULL)
1237
	return NULL;
1238

1239
1240
1241
    /* 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
1242
	full_path[strlen(full_path) - 1] != '/') {
1243
	free(full_path);
1244
	return NULL;
1245
    }
1246

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1247
    /* Otherwise, return the full path. */
1248
1249
1250
    return full_path;
}

1251
1252
/* This function calls mkstemp(($TMPDIR|P_tmpdir|/tmp/)"nano.XXXXXX").
 * On success, it returns the malloc()ed filename and corresponding FILE
1253
 * stream, opened in "r+b" mode.  On error, it returns NULL for the
1254
1255
 * filename and leaves the FILE stream unchanged. */
char *safe_tempfile(FILE **f)
1256
{
1257
    char *full_tempdir = NULL;
1258
1259
1260
    const char *tmpdir_env;
    int fd;
    mode_t original_umask = 0;
1261

1262
1263
    assert(f != NULL);

1264
1265
1266
    /* 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. */
1267
    tmpdir_env = getenv("TMPDIR");
1268
    if (tmpdir_env != NULL)
1269
	full_tempdir = check_writable_directory(tmpdir_env);
1270

1271
1272
    /* If $TMPDIR is unset, empty, or not a writable directory, and
     * full_tempdir is NULL, try P_tmpdir instead. */
1273
    if (full_tempdir == NULL)
1274
	full_tempdir = check_writable_directory(P_tmpdir);
1275

1276
1277
1278
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1279

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1280
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1281
    strcat(full_tempdir, "nano.XXXXXX");
1282

1283
1284
1285
1286
1287
1288
1289
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

    if (fd != -1)
	*f = fdopen(fd, "r+b");
1290
1291
1292
    else {
	free(full_tempdir);
	full_tempdir = NULL;
1293
    }
1294

1295
1296
    umask(original_umask);

1297
    return full_tempdir;
1298
}
1299
1300

#ifndef DISABLE_OPERATINGDIR
1301
1302
1303
1304
1305
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1306
    if (operating_dir == NULL)
1307
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1308

1309
1310
1311
    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
1312
     * inaccessible, unset operating_dir. */
1313
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1314
1315
1316
1317
1318
1319
1320
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1321
1322
1323
1324
1325
/* 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)
1326
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1327
1328
1329
1330
    /* full_operating_dir is global for memory 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. */
1331

1332
    char *fullpath;
1333
    bool retval = FALSE;
1334
    const char *whereami1, *whereami2 = NULL;
1335

1336
    /* If no operating directory is set, don't bother doing anything. */
1337
    if (operating_dir == NULL)
1338
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1339

1340
    assert(full_operating_dir != NULL);
1341
1342

    fullpath = get_full_path(currpath);
1343

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1344
1345
1346
    /* If fullpath is NULL, it means some directory in the path doesn't
     * exist or is unreadable.  If allow_tabcomp is FALSE, then currpath
     * is what the user typed somewhere.  We don't want to report a
1347
     * non-existent directory as being outside the operating directory,
1348
     * so we return FALSE.  If allow_tabcomp is TRUE, then currpath
1349
1350
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1351
    if (fullpath == NULL)
1352
	return allow_tabcomp;
1353
1354
1355
1356
1357

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

1358
    /* If both searches failed, we're outside the operating directory.
1359
     * Otherwise, check the search results.  If the full operating
1360
1361
1362
     * 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. */
1363
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1364
1365
	retval = TRUE;
    free(fullpath);
1366
1367

    /* Otherwise, we're still inside it. */
1368
    return retval;
1369
}
1370
1371
#endif

1372
#ifndef NANO_TINY
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
/* Although this sucks, it sucks less than having a single 'my system is messed up
 * and I'm blanket allowing insecure file writing operations.
 */

int prompt_failed_backupwrite(const char *filename)
{
    static int i;
    static char *prevfile = NULL; /* What was the laast file we were paased so we don't keep asking this?
                                     though maybe we should.... */
    if (prevfile == NULL || strcmp(filename, prevfile)) {
	i = do_yesno_prompt(FALSE,
                         _("Failed to write backup file, continue saving? (Say N if unsure) "));
	prevfile = mallocstrcpy(prevfile, filename);
    }
    return i;
}

1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
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

1413
/* Read from inn, write to out.  We assume inn is opened for reading,
1414
1415
 * and out for writing.  We return 0 on success, -1 on read error, or -2
 * on write error. */
1416
1417
int copy_file(FILE *inn, FILE *out)
{
1418
    int retval = 0;
1419
    char buf[BUFSIZ];
1420
1421
    size_t charsread;

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

1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
    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
1435

1436
1437
1438
1439
    if (fclose(inn) == EOF)
	retval = -1;
    if (fclose(out) == EOF)
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1440

1441
1442
1443
    return retval;
}

1444
1445
/* Write a file out to disk.  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
1446
 * ourselves.  If tmp is TRUE, we set the umask to disallow anyone else
1447
1448
 * 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
1449
 *
1450
 * tmp means we are writing a temporary file in a secure fashion.  We
1451
1452
1453
1454
 * use it when spell checking or dumping the file on an error.  If
 * append is APPEND, it means we are appending instead of overwriting.
 * If append is PREPEND, it means we are prepending instead of
 * overwriting.  If nonamechange is TRUE, we don't change the current
1455
1456
 * filename.  nonamechange is ignored if tmp is FALSE, we're appending,
 * or we're prepending.
1457
 *
1458
1459
 * Return TRUE on success or FALSE on error. */
bool write_file(const char *name, FILE *f_open, bool tmp, append_type
1460
	append, bool nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1461
{
1462
    bool retval = FALSE;
1463
	/* Instead of returning in this function, you should always
1464
	 * set retval and then goto cleanup_and_exit. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1465
    size_t lineswritten = 0;
1466
    const filestruct *fileptr = openfile->fileage;
1467
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1468
	/* The file descriptor we use. */
1469
    mode_t original_umask = 0;
1470
	/* Our umask, from when nano started. */
1471
    bool realexists;
1472
	/* The result of stat().  TRUE if the file exists, FALSE
1473
	 * otherwise.  If name is a link that points nowhere, realexists
1474
	 * is FALSE. */
1475
1476
    struct stat st;
	/* The status fields filled in by stat(). */
1477
    bool anyexists;
1478
1479
	/* The result of lstat().  The same as realexists, unless name
	 * is a link. */
1480
1481
1482
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1483
	/* name after tilde expansion. */
1484
    FILE *f = NULL;
1485
1486
1487
	/* The actual file, realname, we are writing to. */
    char *tempname = NULL;
	/* The temp file name we write to on prepend. */
1488
    int backup_cflags;
Chris Allegretta's avatar
Chris Allegretta committed
1489

1490
    assert(name != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1491

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1492
    if (*name == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1493
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1494

1495
1496
1497
    if (f_open != NULL)
	f = f_open;

1498
1499
    if (!tmp)
	titlebar(NULL);
1500

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1501
    realname = real_dir_from_tilde(name);
1502

1503
#ifndef DISABLE_OPERATINGDIR
1504
    /* If we're writing a temporary file, we're probably going outside
1505
     * the operating directory, so skip the operating directory test. */
1506
    if (!tmp && check_operating_dir(realname, FALSE)) {
1507
1508
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1509
1510
1511
    }
#endif

1512
    anyexists = (lstat(realname, &lst) != -1);
1513

1514
1515
    /* If the temp file exists and isn't already open, give up. */
    if (tmp && anyexists && f_open == NULL)
1516
	goto cleanup_and_exit;
1517

1518
1519
1520
    /* 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)) {
1521
1522
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1523
1524
1525
	goto cleanup_and_exit;
    }

1526
    /* Save the state of the file at the end of the symlink (if there is
1527
     * one). */
1528
    realexists = (stat(realname, &st) != -1);
1529

1530
#ifndef NANO_TINY
1531
    /* if we have not stat()d this file before (say, the user just
1532
1533
     * specified it interactively), stat and save the value
     * or else we will chase null pointers when we do
1534
     * modtime checks, preserve file times, etc. during backup */
1535
1536
    if (openfile->current_stat == NULL && !tmp && realexists)
	stat(realname, openfile->current_stat);
1537

1538
    /* We backup only if the backup toggle is set, the file isn't
1539
1540
1541
1542
     * 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. */
1543
    if (ISSET(BACKUP_FILE) && !tmp && realexists && ((append !=
1544
1545
	OVERWRITE || openfile->mark_set) || (openfile->current_stat &&
	openfile->current_stat->st_mtime == st.st_mtime))) {
1546
	int backup_fd;
1547
	FILE *backup_file;
1548
	char *backupname;
1549
	struct utimbuf filetime;
1550
	int copy_status;
1551

1552
	/* Save the original file's access and modification times. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1553
1554
	filetime.actime = openfile->current_stat->st_atime;
	filetime.modtime = openfile->current_stat->st_mtime;
1555

1556
1557
1558
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1559

1560
1561
1562
	    if (f == NULL) {
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1563
		beep();
1564
1565
1566
1567
		/* 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;
1568
	    }
1569
1570
	}

1571
	/* If backup_dir is set, we set backupname to
1572
1573
1574
1575
	 * 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]. */
1576
	if (backup_dir != NULL) {
1577
	    char *backuptemp = get_full_path(realname);
1578

1579
	    if (backuptemp == NULL)
1580
1581
1582
1583
1584
1585
		/* 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~. */
1586
		backuptemp = mallocstrcpy(NULL, tail(realname));
1587
	    else {
1588
1589
		size_t i = 0;

1590
1591
1592
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1593
1594
1595
1596
		}
	    }

	    backupname = charalloc(strlen(backup_dir) +
1597
1598
1599
1600
		strlen(backuptemp) + 1);
	    sprintf(backupname, "%s%s", backup_dir, backuptemp);
	    free(backuptemp);
	    backuptemp = get_next_filename(backupname, "~");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1601
	    if (*backuptemp == '\0') {
1602
		statusbar(_("Error writing backup file %s: %s"), backupname,
1603
1604
1605
		    _("Too many backup files?"));
		free(backuptemp);
		free(backupname);
1606
1607
1608
1609
1610
		/* If we can't write to the backup, DONT go on, since
		   whatever caused the backup file to fail (e.g. disk
		   full may well cause the real file write to fail, which
		   means we could lose both the backup and the original! */
		goto cleanup_and_exit;
1611
1612
1613
1614
	    } else {
		free(backupname);
		backupname = backuptemp;
	    }
1615
1616
1617
1618
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1619

1620
1621
1622
	/* First, unlink any existing backups.  Next, open the backup
	   file with O_CREAT and O_EXCL.  If it succeeds, we
	   have a file descriptor to a new backup file. */
1623
	if (unlink(backupname) < 0 && errno != ENOENT && !ISSET(INSECURE_BACKUP)) {
1624
1625
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1626
1627
	    statusbar(_("Error writing backup file %s: %s"), backupname,
			strerror(errno));
1628
1629
1630
1631
	    free(backupname);
	    goto cleanup_and_exit;
	}

1632
1633
1634
1635
1636
1637
	if (ISSET(INSECURE_BACKUP))
	    backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
        else
	    backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

	backup_fd = open(backupname, backup_cflags,
1638
1639
1640
1641
		S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
	/* Now we've got a safe file stream.  If the previous open()
	   call failed, this will return NULL. */
	backup_file = fdopen(backup_fd, "wb");
1642

1643
1644
1645
1646
1647
1648
	if (backup_fd < 0 || backup_file == NULL) {
	    statusbar(_("Error writing backup file %s: %s"), backupname,
			strerror(errno));
	    free(backupname);
	    goto cleanup_and_exit;
	}
1649

1650
1651
1652
        /* We shouldn't worry about chown()ing something if we're not
	   root, since it's likely to fail! */
	if (geteuid() == NANO_ROOT_UID && fchown(backup_fd,
1653
		openfile->current_stat->st_uid, openfile->current_stat->st_gid) == -1
1654
1655
1656
		&& !ISSET(INSECURE_BACKUP)) {
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1657
1658
1659
1660
1661
1662
1663
	    statusbar(_("Error writing backup file %s: %s"), backupname,
		strerror(errno));
	    free(backupname);
	    fclose(backup_file);
	    goto cleanup_and_exit;
	}

1664
1665
1666
1667
	if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1
		&& !ISSET(INSECURE_BACKUP)) {
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1668
	    statusbar(_("Error writing backup file %s: %s"), backupname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1669
		strerror(errno));
1670
	    free(backupname);
1671
	    fclose(backup_file);
1672
1673
1674
1675
1676
	    /* If we can't write to the backup, DONT go on, since
	       whatever caused the backup file to fail (e.g. disk
	       full may well cause the real file write to fail, which
	       means we could lose both the backup and the original! */
	    goto cleanup_and_exit;
1677
1678
1679
	}

#ifdef DEBUG
1680
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1681
1682
#endif

1683
1684
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1685

1686
1687
	if (copy_status != 0) {
	    statusbar(_("Error reading %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1688
			strerror(errno));
1689
1690
1691
1692
1693
1694
	    beep();
	    goto cleanup_and_exit;
	}

	/* And set its metadata. */
	if (utime(backupname, &filetime) == -1 && !ISSET(INSECURE_BACKUP)) {
1695
1696
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1697
	    statusbar(_("Error writing backup file %s: %s"), backupname,
1698
			strerror(errno));
1699
1700
1701
1702
1703
	    /* If we can't write to the backup, DONT go on, since
	       whatever caused the backup file to fail (e.g. disk
	       full may well cause the real file write to fail, which
	       means we could lose both the backup and the original! */
	    goto cleanup_and_exit;
1704
	}
1705

1706
1707
	free(backupname);
    }
1708

1709
    skip_backup:
1710
#endif /* !NANO_TINY */
1711

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1712
1713
    /* 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
1714
1715
1716
1717
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1718
	goto cleanup_and_exit;
1719
    }
1720

1721
1722
    if (f_open == NULL) {
	original_umask = umask(0);
1723

1724
	/* If we create a temp file, we don't let anyone else access it.
1725
1726
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1727
	    umask(S_IRWXG | S_IRWXO);
1728
1729
	else
	    umask(original_umask);
1730
    }
1731

1732
    /* If we're prepending, copy the file to a temp file. */
1733
    if (append == PREPEND) {
1734
1735
1736
	int fd_source;
	FILE *f_source = NULL;

1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
	if (f == NULL) {
	    f = fopen(realname, "rb");

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

1748
	tempname = safe_tempfile(&f);
1749

1750
	if (tempname == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1751
	    statusbar(_("Error writing temp file: %s"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1752
		strerror(errno));
1753
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1754
	}
1755

1756
	if (f_open == NULL) {
1757
	    fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1758

1759
1760
1761
1762
1763
	    if (fd_source != -1) {
		f_source = fdopen(fd_source, "rb");
		if (f_source == NULL) {
		    statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1764
		    beep();
1765
1766
1767
1768
1769
1770
		    close(fd_source);
		    fclose(f);
		    unlink(tempname);
		    goto cleanup_and_exit;
		}
	    }
1771
1772
1773
	}

	if (copy_file(f_source, f) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1774
1775
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1776
	    unlink(tempname);
1777
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1778
1779
1780
	}
    }

1781
1782
1783
    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
1784
1785
1786
	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);
1787

1788
1789
	/* Set the umask back to the user's original value. */
	umask(original_umask);
1790

1791
1792
1793
1794
	/* If we couldn't open the file, give up. */
	if (fd == -1) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1795

1796
1797
1798
1799
1800
	    /* tempname has been set only if we're prepending. */
	    if (tempname != NULL)
		unlink(tempname);
	    goto cleanup_and_exit;
	}
1801

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

1804
1805
1806
1807
1808
1809
	if (f == NULL) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
	    close(fd);
	    goto cleanup_and_exit;
	}
1810
1811
    }

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

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

1819
	/* Convert newlines to nulls, just before we write to disk. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1820
1821
	sunder(fileptr->data);

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

1824
1825
	/* Convert nulls to newlines.  data_len is the string's real
	 * length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1826
1827
	unsunder(fileptr->data, data_len);

1828
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1829
1830
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1831
	    fclose(f);
1832
	    goto cleanup_and_exit;
1833
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1834

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1835
	/* If we're on the last line of the file, don't write a newline
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1836
1837
1838
1839
1840
1841
1842
	 * 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 {
1843
#ifndef NANO_TINY
1844
1845
1846
1847
	    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
1848
			strerror(errno));
1849
1850
1851
		    fclose(f);
		    goto cleanup_and_exit;
		}
1852
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1853

1854
	    if (openfile->fmt != MAC_FILE) {
1855
#endif
1856
1857
		if (putc('\n', f) == EOF) {
		    statusbar(_("Error writing %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1858
			strerror(errno));
1859
1860
1861
		    fclose(f);
		    goto cleanup_and_exit;
		}
1862
#ifndef NANO_TINY
1863
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1864
#endif
1865
	}
Chris Allegretta's avatar
Chris Allegretta committed
1866
1867
1868
1869
1870

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

1871
    /* If we're prepending, open the temp file, and append it to f. */
1872
    if (append == PREPEND) {
1873
1874
1875
	int fd_source;
	FILE *f_source = NULL;

1876
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1877

1878
1879
1880
1881
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1882
	}
1883

1884
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1885
1886
	    statusbar(_("Error reading %s: %s"), tempname,
		strerror(errno));
1887
	    beep();
1888
	    fclose(f);
1889
	    goto cleanup_and_exit;
1890
1891
	}

1892
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1893
1894
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1895
	    goto cleanup_and_exit;
1896
	}
1897
1898
1899
1900
1901
    } else if (fclose(f) != 0) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
	    goto cleanup_and_exit;
    }
1902

1903
    if (!tmp && append == OVERWRITE) {
Chris Allegretta's avatar
Chris Allegretta committed
1904
	if (!nonamechange) {
1905
1906
	    openfile->filename = mallocstrcpy(openfile->filename,
		realname);
Chris Allegretta's avatar
Chris Allegretta committed
1907
#ifdef ENABLE_COLOR
1908
	    /* We might have changed the filename, so update the colors
1909
1910
	     * to account for it, and then make sure we're using
	     * them. */
1911
	    color_update();
1912
	    color_init();
1913
1914
1915
1916
1917

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

1922
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1923
1924
1925
1926
1927
	/* 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);
1928
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1929

1930
1931
	statusbar(P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten),
1932
		(unsigned long)lineswritten);
1933
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1934
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1935
    }
1936

1937
    retval = TRUE;
1938
1939
1940

  cleanup_and_exit:
    free(realname);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1941
1942
    if (tempname != NULL)
	free(tempname);
1943

1944
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1945
1946
}

1947
#ifndef NANO_TINY
1948
1949
/* Write a marked selection from a file out to disk.  Return TRUE on
 * success or FALSE on error. */
1950
bool write_marked_file(const char *name, FILE *f_open, bool tmp,
1951
	append_type append)
1952
{
1953
    bool retval;
1954
1955
    bool old_modified = openfile->modified;
	/* write_file() unsets the modified flag. */
1956
    bool added_magicline = FALSE;
1957
1958
1959
1960
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

1961
1962
    assert(openfile->mark_set);

1963
1964
1965
    /* Partition the filestruct so that it contains only the marked
     * text. */
    mark_order((const filestruct **)&top, &top_x,
1966
	(const filestruct **)&bot, &bot_x, NULL);
1967
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1968

1969
1970
1971
1972
1973
1974
    /* 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')))
1975
	new_magicline();
1976

1977
    retval = write_file(name, f_open, tmp, append, TRUE);
1978

1979
1980
1981
    /* If the NO_NEWLINES flag isn't set, and we added a magicline,
     * remove it now. */
    if (!ISSET(NO_NEWLINES) && added_magicline)
1982
1983
1984
1985
	remove_magicline();

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

1988
    if (old_modified)
1989
1990
1991
1992
	set_modified();

    return retval;
}
1993
#endif /* !NANO_TINY */
1994

1995
1996
1997
/* 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
1998
1999
 * TEMP_FILE flag is set.  Return TRUE on success or FALSE on error. */
bool do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
2000
{
2001
    int i;
2002
    append_type append = OVERWRITE;
2003
    char *ans;
2004
	/* The last answer the user typed at the statusbar prompt. */
2005
#ifdef NANO_EXTRA
2006
    static bool did_credits = FALSE;
2007
#endif
2008
2009
    bool retval = FALSE, meta_key = FALSE, func_key = FALSE;
    const sc *s;
Chris Allegretta's avatar
Chris Allegretta committed
2010

2011
    currmenu = MWRITEFILE;
2012

2013
    if (exiting && openfile->filename[0] != '\0' && ISSET(TEMP_FILE)) {
2014
	retval = write_file(openfile->filename, NULL, FALSE, OVERWRITE,
2015
		FALSE);
2016
2017

	/* Write succeeded. */
2018
	if (retval)
2019
	    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2020
2021
    }

2022
    ans = mallocstrcpy(NULL,
2023
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2024
	(!exiting && openfile->mark_set) ? "" :
2025
#endif
2026
	openfile->filename);
2027
2028
2029

    while (TRUE) {
	const char *msg;
2030
#ifndef NANO_TINY
2031
2032
	const char *formatstr, *backupstr;

2033
	formatstr = (openfile->fmt == DOS_FILE) ?
2034
2035
		_(" [DOS Format]") : (openfile->fmt == MAC_FILE) ?
		_(" [Mac Format]") : "";
2036

2037
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2038

2039
	/* If we're using restricted mode, don't display the "Write
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2040
2041
2042
	 * Selection to File" prompt.  This function is disabled, since
	 * it allows reading from or writing to files not specified on
	 * the command line. */
2043
	if (!ISSET(RESTRICTED) && !exiting && openfile->mark_set)
2044
	    msg = (append == PREPEND) ?
2045
2046
2047
		_("Prepend Selection to File") : (append == APPEND) ?
		_("Append Selection to File") :
		_("Write Selection to File");
2048
	else
2049
#endif /* !NANO_TINY */
2050
2051
2052
	    msg = (append == PREPEND) ? _("File Name to Prepend to") :
		(append == APPEND) ? _("File Name to Append to") :
		_("File Name to Write");
2053

2054
2055
2056
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
2057
	i = do_prompt(!ISSET(RESTRICTED) ||
2058
2059
2060
2061
		openfile->filename[0] == '\0',
#ifndef DISABLE_TABCOMP
		TRUE,
#endif
2062
		MWRITEFILE, ans,
2063
		&meta_key, &func_key,
2064
#ifndef NANO_TINY
2065
2066
		NULL,
#endif
2067
		edit_refresh, "%s%s%s", msg,
2068
2069
#ifndef NANO_TINY
		formatstr, backupstr
2070
#else
2071
		"", ""
2072
2073
2074
#endif
		);

2075
2076
	/* If the filename or command begins with a newline (i.e. an
	 * encoded null), treat it as though it's blank. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2077
	if (i < 0 || *answer == '\n') {
2078
	    statusbar(_("Cancelled"));
2079
	    retval = FALSE;
2080
2081
2082
	    break;
	} else {
	    ans = mallocstrcpy(ans, answer);
2083
            s = get_shortcut(currmenu, &i, &meta_key, &func_key);
Chris Allegretta's avatar
Chris Allegretta committed
2084

2085
#ifndef DISABLE_BROWSER
2086
	    if (s && s->scfunc == to_files_void) {
2087
		char *tmp = do_browse_from(answer);
2088

2089
2090
		if (tmp == NULL)
		    continue;
2091

2092
		/* We have a file now.  Indicate this. */
2093
2094
2095
		free(answer);
		answer = tmp;
	    } else
2096
#endif /* !DISABLE_BROWSER */
2097
#ifndef NANO_TINY
2098
	    if (s && s->scfunc == dos_format_void) {
2099
2100
		openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE :
			DOS_FILE;
2101
		continue;
2102
	    } else if (s && s->scfunc == mac_format_void) {
2103
2104
		openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE :
			MAC_FILE;
2105
		continue;
2106
	    } else if (s && s->scfunc == backup_file_void) {
2107
2108
2109
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
2110
#endif /* !NANO_TINY */
2111
	    if (s && s->scfunc == prepend_void) {
2112
		append = (append == PREPEND) ? OVERWRITE : PREPEND;
2113
		continue;
2114
	    } else if (s && s->scfunc == append_void) {
2115
		append = (append == APPEND) ? OVERWRITE : APPEND;
2116
		continue;
2117
	    } else if (s && s->scfunc == do_help_void) {
2118
		continue;
2119
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2120

Chris Allegretta's avatar
Chris Allegretta committed
2121
#ifdef DEBUG
2122
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
2123
#endif
2124
2125

#ifdef NANO_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2126
2127
2128
2129
2130
2131
2132
2133
	    /* 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) {
2134
		do_credits();
2135
		did_credits = TRUE;
2136
		retval = FALSE;
2137
2138
		break;
	    }
2139
#endif
2140

2141
	    if (append == OVERWRITE) {
2142
		size_t answer_len = strlen(answer);
2143
		bool name_exists, do_warning;
2144
		char *full_answer, *full_filename;
2145
2146
		struct stat st;

2147
2148
2149
		/* Convert newlines to nulls, just before we get the
		 * full path. */
		sunder(answer);
2150

2151
2152
		full_answer = get_full_path(answer);
		full_filename = get_full_path(openfile->filename);
2153
2154
		name_exists = (stat((full_answer == NULL) ? answer :
			full_answer, &st) != -1);
2155
2156
2157
2158
2159
2160
		if (openfile->filename[0] == '\0')
		    do_warning = name_exists;
		else
		    do_warning = (strcmp((full_answer == NULL) ?
			answer : full_answer, (full_filename == NULL) ?
			openfile->filename : full_filename) != 0);
2161

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2162
2163
2164
2165
		/* Convert nulls to newlines.  answer_len is the
		 * string's real length. */
		unsunder(answer, answer_len);

2166
2167
2168
2169
		if (full_filename != NULL)
		    free(full_filename);
		if (full_answer != NULL)
		    free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2170

2171
		if (do_warning) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2172
2173
2174
2175
2176
2177
2178
2179
		    /* If we're using restricted mode, we aren't allowed
		     * to overwrite an existing file with the current
		     * file.  We also aren't allowed to change the name
		     * of the current file if it has one, because that
		     * would allow reading from or writing to files not
		     * specified on the command line. */
		    if (ISSET(RESTRICTED))
			continue;
2180

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2181
		    if (name_exists) {
2182
2183
2184
2185
			i = do_yesno_prompt(FALSE,
				_("File exists, OVERWRITE ? "));
			if (i == 0 || i == -1)
			    continue;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2186
		    } else
2187
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2188
		    if (exiting || !openfile->mark_set)
2189
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2190
		    {
2191
2192
2193
2194
2195
			i = do_yesno_prompt(FALSE,
				_("Save file under DIFFERENT NAME ? "));
			if (i == 0 || i == -1)
			    continue;
		    }
2196
		}
2197
#ifndef NANO_TINY
2198
2199
2200
		/* Complain if the file exists, the name hasn't changed, and the
		    stat information we had before does not match what we have now */
		else if (name_exists && openfile->current_stat && (openfile->current_stat->st_mtime < st.st_mtime ||
2201
                    openfile->current_stat->st_dev != st.st_dev || openfile->current_stat->st_ino != st.st_ino)) {
2202
2203
2204
2205
2206
		    i = do_yesno_prompt(FALSE,
			_("File was modified since you opened it, continue saving ? "));
		    if (i == 0 || i == -1)
			continue;
		}
2207
#endif
2208

2209
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2210

2211
2212
2213
2214
2215
	    /* Convert newlines to nulls, just before we save the
	     * file. */
	    sunder(answer);
	    align(&answer);

2216
	    /* Here's where we allow the selected text to be written to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2217
2218
2219
	     * a separate file.  If we're using restricted mode, this
	     * function is disabled, since it allows reading from or
	     * writing to files not specified on the command line. */
2220
2221
2222
2223
	    retval =
#ifndef NANO_TINY
		(!ISSET(RESTRICTED) && !exiting && openfile->mark_set) ?
		write_marked_file(answer, NULL, FALSE, append) :
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2224
#endif
2225
		write_file(answer, NULL, FALSE, append, FALSE);
2226

2227
2228
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2229
    }
2230
2231

    free(ans);
2232

2233
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2234
2235
}

2236
2237
/* Write the current file to disk.  If the mark is on, write the current
 * marked selection to disk. */
2238
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
2239
{
2240
    do_writeout(FALSE);
2241
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
2242
}
Chris Allegretta's avatar
Chris Allegretta committed
2243

2244
2245
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
2246
char *real_dir_from_tilde(const char *buf)
2247
{
2248
    char *retval;
2249

2250
    assert(buf != NULL);
2251

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2252
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2253
	size_t i = 1;
2254
	char *tilde_dir;
2255

2256
	/* Figure out how much of the string we need to compare. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2257
	for (; buf[i] != '/' && buf[i] != '\0'; i++)
2258
2259
	    ;

2260
2261
2262
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
2263
	    tilde_dir = mallocstrcpy(NULL, homedir);
2264
2265
2266
	} else {
	    const struct passwd *userdata;

2267
2268
2269
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

2270
2271
	    do {
		userdata = getpwent();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2272
2273
	    } while (userdata != NULL && strcmp(userdata->pw_name,
		tilde_dir + 1) != 0);
2274
	    endpwent();
2275
	    if (userdata != NULL)
2276
		tilde_dir = mallocstrcpy(tilde_dir, userdata->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2277
	}
2278

2279
2280
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2281

2282
2283
2284
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2285

2286
    return retval;
2287
2288
}

2289
#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
2290
/* Our sort routine for file listings.  Sort alphabetically and
2291
 * case-insensitively, and sort directories before filenames. */
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
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;

2305
2306
2307
2308
    /* 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, */
2309
    return mbstrcasecmp(a, b);
2310
}
2311
2312
2313
2314
2315

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

2318
2319
2320
2321
    for (; len > 0; len--)
	free(array[len - 1]);
    free(array);
}
2322
2323
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2324
#ifndef DISABLE_TABCOMP
2325
/* Is the given path a directory? */
2326
bool is_dir(const char *buf)
2327
{
2328
    char *dirptr;
2329
    struct stat fileinfo;
2330
2331
2332
    bool retval;

    assert(buf != NULL);
2333

2334
2335
2336
2337
    dirptr = real_dir_from_tilde(buf);

    retval = (stat(dirptr, &fileinfo) != -1 &&
	S_ISDIR(fileinfo.st_mode));
2338

2339
    free(dirptr);
2340

2341
    return retval;
2342
}
Chris Allegretta's avatar
Chris Allegretta committed
2343

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2344
2345
2346
/* 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
2347
 * file, with the copyright years updated:
Chris Allegretta's avatar
Chris Allegretta committed
2348
2349
2350
 *
 * Termios command line History and Editting, originally
 * intended for NetBSD sh (ash)
2351
 * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
Chris Allegretta's avatar
Chris Allegretta committed
2352
2353
2354
2355
2356
2357
2358
2359
 *      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.
2360
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
2361

2362
/* We consider the first buf_len characters of buf for ~username tab
2363
2364
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
2365
	size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2366
{
2367
2368
    char **matches = NULL;
    const struct passwd *userdata;
2369

2370
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2371

2372
    *num_matches = 0;
2373

2374
    while ((userdata = getpwent()) != NULL) {
2375
	if (strncmp(userdata->pw_name, buf + 1, buf_len - 1) == 0) {
2376
2377
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2378

2379
2380
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2381
2382
2383
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2384
2385
#endif

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2386
2387
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
2388
2389
2390
	    matches[*num_matches] =
		charalloc(strlen(userdata->pw_name) + 2);
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2391
	    ++(*num_matches);
2392
	}
2393
2394
    }
    endpwent();
2395

2396
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2397
2398
}

2399
/* We consider the first buf_len characters of buf for filename tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2400
 * completion. */
2401
char **cwd_tab_completion(const char *buf, bool allow_files, size_t
2402
	*num_matches, size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2403
{
2404
    char *dirname = mallocstrcpy(NULL, buf), *filename;
2405
2406
2407
2408
2409
#ifndef DISABLE_OPERATINGDIR
    size_t dirnamelen;
#endif
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2410
    DIR *dir;
2411
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2412

2413
    assert(dirname != NULL && num_matches != NULL);
2414

2415
    *num_matches = 0;
2416
    null_at(&dirname, buf_len);
2417
2418
2419
2420
2421
2422
2423
2424

    /* 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
2425
	*tmpdirname = '\0';
2426
2427
2428
	tmpdirname = dirname;
	dirname = real_dir_from_tilde(dirname);
	free(tmpdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2429
    } else {
2430
2431
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2432
2433
    }

2434
    assert(dirname[strlen(dirname) - 1] == '/');
2435

Chris Allegretta's avatar
Chris Allegretta committed
2436
    dir = opendir(dirname);
2437

2438
    if (dir == NULL) {
2439
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2440
	beep();
2441
2442
2443
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2444
    }
2445
2446
2447
2448
2449
2450

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

2451
    while ((nextdir = readdir(dir)) != NULL) {
2452
2453
	bool skip_match = FALSE;

Chris Allegretta's avatar
Chris Allegretta committed
2454
#ifdef DEBUG
2455
	fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2456
#endif
2457
	/* See if this matches. */
2458
	if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
2459
2460
		(*filename == '.' || (strcmp(nextdir->d_name, ".") !=
		0 && strcmp(nextdir->d_name, "..") != 0))) {
2461
2462
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2463

2464
2465
2466
2467
	    char *tmp = charalloc(strlen(dirname) +
		strlen(nextdir->d_name) + 1);
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

2468
2469
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2470
2471
2472
2473
2474
2475
2476
	     * 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. */
2477
	    if (!allow_files && !is_dir(tmp))
2478
2479
2480
		skip_match = TRUE;

	    free(tmp);
2481

2482
	    if (skip_match)
2483
		continue;
2484

2485
2486
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
2487
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2488
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2489
2490
	}
    }
2491

2492
2493
    closedir(dir);
    free(dirname);
2494
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2495

2496
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2497
2498
}

2499
/* Do tab completion.  place refers to how much the statusbar cursor
2500
2501
 * position should be advanced.  refresh_func is the function we will
 * call to refresh the edit window. */
2502
char *input_tab(char *buf, bool allow_files, size_t *place, bool
2503
	*lastwastab, void (*refresh_func)(void), bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2504
{
2505
    size_t num_matches = 0, buf_len;
2506
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2507

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2510
    *list = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2511

2512
2513
    /* If the word starts with `~' and there is no slash in the word,
     * then try completing this word as a username. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2514
    if (*place > 0 && *buf == '~') {
2515
	const char *bob = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2516

2517
2518
2519
2520
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2521

2522
2523
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
2524
2525
	matches = cwd_tab_completion(buf, allow_files, &num_matches,
		*place);
2526

2527
2528
2529
    buf_len = strlen(buf);

    if (num_matches == 0 || *place != buf_len)
2530
2531
2532
2533
	beep();
    else {
	size_t match, common_len = 0;
	char *mzero;
2534
2535
2536
	const char *lastslash = revstrstr(buf, "/", buf + *place);
	size_t lastslash_len = (lastslash == NULL) ? 0 :
		lastslash - buf + 1;
2537
2538
2539
	char *match1_mb = charalloc(mb_cur_max() + 1);
	char *match2_mb = charalloc(mb_cur_max() + 1);
	int match1_mb_len, match2_mb_len;
2540
2541
2542

	while (TRUE) {
	    for (match = 1; match < num_matches; match++) {
2543
2544
		/* Get the number of single-byte characters that all the
		 * matches have in common. */
2545
		match1_mb_len = parse_mbchar(matches[0] + common_len,
2546
			match1_mb, NULL);
2547
		match2_mb_len = parse_mbchar(matches[match] +
2548
			common_len, match2_mb, NULL);
2549
2550
2551
		match1_mb[match1_mb_len] = '\0';
		match2_mb[match2_mb_len] = '\0';
		if (strcmp(match1_mb, match2_mb) != 0)
2552
2553
		    break;
	    }
2554

2555
	    if (match < num_matches || matches[0][common_len] == '\0')
2556
		break;
2557

2558
	    common_len += parse_mbchar(buf + common_len, NULL, NULL);
2559
	}
2560

2561
2562
2563
	free(match1_mb);
	free(match2_mb);

2564
	mzero = charalloc(lastslash_len + common_len + 1);
2565
2566
2567

	strncpy(mzero, buf, lastslash_len);
	strncpy(mzero + lastslash_len, matches[0], common_len);
2568

2569
	common_len += lastslash_len;
2570
	mzero[common_len] = '\0';
2571

2572
	assert(common_len >= *place);
2573

2574
	if (num_matches == 1 && is_dir(mzero)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2575
	    mzero[common_len++] = '/';
2576

2577
2578
	    assert(common_len > *place);
	}
2579

2580
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2581
	    beep();
2582

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2583
	/* If there is more of a match to display on the statusbar, show
2584
	 * it.  We reset lastwastab to FALSE: it requires pressing Tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2585
	 * twice in succession with no statusbar changes to see a match
2586
2587
2588
	 * list. */
	if (common_len != *place) {
	    *lastwastab = FALSE;
2589
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2590
2591
	    charmove(buf + common_len, buf + *place, buf_len -
		*place + 1);
2592
	    strncpy(buf, mzero, common_len);
2593
	    *place = common_len;
2594
	} else if (!*lastwastab || num_matches < 2)
2595
2596
	    *lastwastab = TRUE;
	else {
2597
	    int longest_name = 0, ncols, editline = 0;
2598

2599
2600
2601
2602
2603
2604
2605
2606
	    /* 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
2607

2608
2609
		if (common_len > COLS - 1) {
		    longest_name = COLS - 1;
2610
2611
		    break;
		}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2612

2613
2614
		if (common_len > longest_name)
		    longest_name = common_len;
2615
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2616

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

2619
	    /* Each column will be (longest_name + 2) columns wide, i.e.
2620
2621
	     * two spaces between columns, except that there will be
	     * only one space after the last column. */
2622
	    ncols = (COLS + 1) / (longest_name + 2);
2623

2624
2625
2626
2627
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2628

2629
2630
	    /* Disable el cursor. */
	    curs_set(0);
2631

2632
2633
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2634

2635
		wmove(edit, editline, (longest_name + 2) *
2636
			(match % ncols));
2637

2638
		if (match % ncols == 0 &&
2639
			editline == editwinrows - 1 &&
2640
			num_matches - match > ncols) {
2641
2642
2643
		    waddstr(edit, _("(more)"));
		    break;
		}
2644

2645
2646
2647
2648
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2649

2650
		if ((match + 1) % ncols == 0)
2651
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2652
	    }
2653

2654
	    wnoutrefresh(edit);
2655
	    *list = TRUE;
2656
2657
2658
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2659
2660
    }

2661
    free_chararray(matches, num_matches);
2662

2663
    /* Only refresh the edit window if we don't have a list of filename
2664
     * matches on it. */
2665
    if (!*list)
2666
	refresh_func();
2667
2668

    /* Enable el cursor. */
2669
    curs_set(1);
2670

2671
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2672
}
2673
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2674

2675
2676
/* Only print the last part of a path.  Isn't there a shell command for
 * this? */
2677
2678
const char *tail(const char *foo)
{
2679
    const char *tmp = strrchr(foo, '/');
2680

2681
2682
    if (tmp == NULL)
	tmp = foo;
2683
    else
2684
2685
2686
2687
2688
	tmp++;

    return tmp;
}

2689
#if !defined(NANO_TINY) && defined(ENABLE_NANORC)
2690
/* Return the constructed dorfile path, or NULL if we can't find the home
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2691
2692
 * directory.  The string is dynamically allocated, and should be
 * freed. */
2693
char *construct_filename(const char *str)
2694
{
2695
    char *newstr = NULL;
2696

2697
2698
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2699

2700
2701
2702
	newstr = charalloc(homelen + strlen(str) + 1);
	strcpy(newstr, homedir);
	strcpy(newstr + homelen, str);
Chris Allegretta's avatar
Chris Allegretta committed
2703
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2704

2705
2706
2707
2708
2709
2710
    return newstr;

}

char *histfilename(void)
{
2711
    return construct_filename("/.nano/search_history");
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
}

/* Construct the legacy history filename
 * (Deprecate in 2.5, delete later
 */
char *legacyhistfilename(void)
{
    return construct_filename("/.nano_history");
}

char *poshistfilename(void)
{
2724
    return construct_filename("/.nano/filepos_history");
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
}



void history_error(const char *msg, ...)
{
   va_list ap;

    va_start(ap, msg);
    vfprintf(stderr, _(msg), ap);
    va_end(ap);

    fprintf(stderr, _("\nPress Enter to continue\n"));
    while (getchar() != '\n')
	;

}

/* Now that we have more than one history file, let's just rely
   on a .nano dir for this stuff.  Return 1 if the dir exists
   or was successfully created, and return 0 otherwise.
 */
int check_dotnano(void)
{
    struct stat dirstat;
    char *nanodir = construct_filename("/.nano");

    if (stat(nanodir, &dirstat) == -1) {
	if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
	    history_error(N_("Unable to create directory %s: %s\nIt is required for saving/loading search history or cursor position\n"),
		nanodir, strerror(errno));
	    return 0;
	}
    } else if (!S_ISDIR(dirstat.st_mode)) {
	history_error(N_("Path %s is not a directory and needs to be.\nNano will be unable to load or save search or cursor position history\n"));
	return 0;
    }
    return 1;
2763
2764
}

2765
/* Load histories from ~/.nano_history. */
2766
2767
2768
void load_history(void)
{
    char *nanohist = histfilename();
2769
2770
2771
2772
    char *legacyhist = legacyhistfilename();
    struct stat hstat;


2773
    if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
2774
2775
2776
2777
2778
2779
2780
2781
2782
	if (rename(legacyhist, nanohist)  == -1)
	    history_error(N_("Detected a legacy nano history file (%s) which I tried to move\nto the preferred location (%s) but encountered an error: %s"),
		legacyhist, nanohist, strerror(errno));
	else
	    history_error(N_("Detected a legacy nano history file (%s) which I moved\nto the preferred location (%s)\n(see the nano FAQ about this change)"),
		legacyhist, nanohist);
    }


Chris Allegretta's avatar
Chris Allegretta committed
2783

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

2788
	if (hist == NULL) {
2789
	    if (errno != ENOENT) {
2790
2791
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2792
		history_error(N_("Error reading %s: %s"), nanohist,
2793
			strerror(errno));
2794
	    }
2795
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2796
2797
2798
	    /* Load a history list (first the search history, then the
	     * replace history) from the oldest entry to the newest.
	     * Assume the last history entry is a blank line. */
2799
	    filestruct **history = &search_history;
2800
	    char *line = NULL;
2801
	    size_t buf_len = 0;
2802
2803
	    ssize_t read;

2804
	    while ((read = getline(&line, &buf_len, hist)) >= 0) {
2805
2806
2807
2808
2809
2810
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
2811
2812
		    update_history(history, line);
		} else
2813
2814
		    history = &replace_history;
	    }
2815

2816
	    fclose(hist);
2817
	    free(line);
2818
2819
	    if (search_history->prev != NULL)
		last_search = mallocstrcpy(NULL, search_history->prev->data);
2820
	}
2821
	free(nanohist);
2822
	free(legacyhist);
2823
2824
2825
    }
}

2826
2827
2828
/* 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. */
2829
bool writehist(FILE *hist, filestruct *h)
2830
{
2831
    filestruct *p;
2832

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2833
2834
    /* Write a history list from the oldest entry to the newest.  Assume
     * the last history entry is a blank line. */
2835
    for (p = h; p != NULL; p = p->next) {
2836
	size_t p_len = strlen(p->data);
2837

2838
	sunder(p->data);
2839

2840
	if (fwrite(p->data, sizeof(char), p_len, hist) < p_len ||
2841
2842
		putc('\n', hist) == EOF)
	    return FALSE;
2843
    }
2844

2845
    return TRUE;
2846
2847
}

2848
/* Save histories to ~/.nano/search_history. */
2849
2850
void save_history(void)
{
2851
    char *nanohist;
2852

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2853
    /* Don't save unchanged or empty histories. */
2854
2855
    if (!history_has_changed() || (searchbot->lineno == 1 &&
	replacebot->lineno == 1))
2856
2857
	return;

2858
2859
2860
2861
    nanohist = histfilename();

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

2863
	if (hist == NULL)
2864
	    history_error(N_("Error writing %s: %s"), nanohist,
2865
		strerror(errno));
2866
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2867
2868
	    /* Make sure no one else can read from or write to the
	     * history file. */
2869
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2870

2871
2872
	    if (!writehist(hist, searchage) || !writehist(hist,
		replaceage))
2873
		history_error(N_("Error writing %s: %s"), nanohist,
2874
			strerror(errno));
2875

2876
2877
	    fclose(hist);
	}
2878

2879
2880
2881
	free(nanohist);
    }
}
2882
2883
2884
2885
2886
2887


/* Analogs for the POS history */
void save_poshistory(void)
{
    char *poshist;
2888
2889
    char *statusstr = NULL;
    poshiststruct *posptr;
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903

    poshist = poshistfilename();

    if (poshist != NULL) {
	FILE *hist = fopen(poshist, "wb");

	if (hist == NULL)
	    history_error(N_("Error writing %s: %s"), poshist,
		strerror(errno));
	else {
	    /* Make sure no one else can read from or write to the
	     * history file. */
	    chmod(poshist, S_IRUSR | S_IWUSR);

2904
2905
2906
2907
            for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
		statusstr = charalloc(strlen(posptr->filename) + 2 * sizeof(ssize_t) + 4);
		sprintf(statusstr, "%s %d %d\n", posptr->filename, (int) posptr->lineno,
			(int) posptr->xno);
2908
2909
2910
		if (fwrite(statusstr, sizeof(char), strlen(statusstr), hist) < strlen(statusstr))
		    history_error(N_("Error writing %s: %s"), poshist,
			strerror(errno));
2911
		free(statusstr);
2912
2913
2914
2915
2916
2917
2918
	    }
	    fclose(hist);
	}
	free(poshist);
    }
}

2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
/* Update the POS history, given a filename line and column.
 * If no entry is found, add a new entry on the end
 */
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
   poshiststruct *posptr, *posprev = NULL;
   char *fullpath = get_full_path(filename);

    if (fullpath == NULL)
        return;

    for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
        if (!strcmp(posptr->filename, fullpath)) {
	    posptr->lineno = lineno;
	    posptr->xno = xpos;
            return;
        }
	posprev = posptr;
    }

    /* Didn't find it, make a new node yo! */

2941
    posptr = (poshiststruct *) nmalloc(sizeof(poshiststruct));
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
    posptr->filename = mallocstrcpy(NULL, fullpath);
    posptr->lineno = lineno;
    posptr->xno = xpos;
    posptr->next = NULL;

    if (!poshistory)
	poshistory = posptr;
    else
	posprev->next = posptr;

    free(fullpath);
}


/* Check the POS history to see if file matches
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
 * an existing entry.  If so return 1 and set line and column
 * to the right values  Otherwise return 0
 */
int check_poshistory(const char *file, ssize_t *line, ssize_t *column)
{
    poshiststruct *posptr;
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
    	return 0;

    for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
	if (!strcmp(posptr->filename, fullpath)) {
	    *line = posptr->lineno;
	    *column = posptr->xno;
2972
	    free(fullpath);
2973
2974
2975
	    return 1;
	}
    }
2976
    free(fullpath);
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
    return 0;
}


/* Load histories from ~/.nano_history. */
void load_poshistory(void)
{
    char *nanohist = poshistfilename();

    /* Assume do_rcfile() has reported a missing home directory. */
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "rb");

	if (hist == NULL) {
	    if (errno != ENOENT) {
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
		history_error(N_("Error reading %s: %s"), nanohist,
			strerror(errno));
	    }
	} else {
	    char *line = NULL, *lineptr, *xptr;
	    size_t buf_len = 0;
	    ssize_t read, lineno, xno;
	    poshiststruct *posptr;

	    /* See if we can find the file we're currently editing */
	    while ((read = getline(&line, &buf_len, hist)) >= 0) {
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
		}
		lineptr = parse_next_word(line);
		xptr = parse_next_word(lineptr);
		lineno = atoi(lineptr);
		xno = atoi(xptr);
		if (poshistory == NULL) {
3017
		    poshistory = (poshiststruct *) nmalloc(sizeof(poshiststruct));
3018
3019
3020
3021
3022
		    poshistory->filename = mallocstrcpy(NULL, line);
		    poshistory->lineno = lineno;
		    poshistory->xno = xno;
		    poshistory->next = NULL;
		} else {
3023
		    for (posptr = poshistory; posptr->next != NULL; posptr = posptr->next)
3024
			;
3025
		    posptr->next = (poshiststruct *) nmalloc(sizeof(poshiststruct));
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
		    posptr->next->filename = mallocstrcpy(NULL, line);
		    posptr->next->lineno = lineno;
		    posptr->next->xno = xno;
		    posptr->next->next = NULL;
		}

	    }

	    fclose(hist);
	    free(line);
	}
	free(nanohist);
    }
}

3041
#endif /* !NANO_TINY && ENABLE_NANORC */