files.c 92.5 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   files.c                                                              *
 *                                                                        *
5
 *   Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,  *
6
 *   2008, 2009, 2010, 2011, 2013, 2014 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>
34
#include <libgen.h>
Chris Allegretta's avatar
Chris Allegretta committed
35

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

54
    /* Start initializing the new buffer. */
55
    openfile->filename = mallocstrcpy(NULL, "");
Chris Allegretta's avatar
Chris Allegretta committed
56

57
    initialize_buffer_text();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
58

59
60
    openfile->current_x = 0;
    openfile->placewewant = 0;
61
    openfile->current_y = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
62

63
    openfile->modified = FALSE;
64
#ifndef NANO_TINY
65
    openfile->mark_set = FALSE;
66
67
68
    openfile->mark_begin = NULL;
    openfile->mark_begin_x = 0;

69
    openfile->fmt = NIX_FILE;
70

71
72
    openfile->undotop = NULL;
    openfile->current_undo = NULL;
73
    openfile->last_action = OTHER;
74
75

    openfile->current_stat = NULL;
76
    openfile->lock_filename = NULL;
77
#endif
78
#ifndef DISABLE_COLOR
79
    openfile->syntax = NULL;
80
81
    openfile->colorstrings = NULL;
#endif
82
}
Chris Allegretta's avatar
Chris Allegretta committed
83

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

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

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

97
#ifndef DISABLE_COLOR
98
99
100
    openfile->fileage->multidata = NULL;
#endif

101
    openfile->totsize = 0;
102
103
}

104
#ifndef NANO_TINY
105
106
107
108
109
110
111
112
113
114
115
/* Actually write the lockfile.  This function will ALWAYS annihilate
 * any previous version of the file.  We'll borrow INSECURE_BACKUP here
 * to decide about lockfile paranoia here as well...
 *
 * Args:
 *     lockfilename: file name for lock
 *     origfilename: name of the file the lock is for
 *     modified: whether to set the modified bit in the file
 *
 * Returns: 1 on success, 0 on failure (but continue loading), -1 on
 * failure and abort. */
116
117
118
119
120
121
122
int write_lockfile(const char *lockfilename, const char *origfilename, bool modified)
{
    int cflags, fd;
    FILE *filestream;
    pid_t mypid;
    uid_t myuid;
    struct passwd *mypwuid;
123
    struct stat fileinfo;
124
125
126
127
128
    char *lockdata = charalloc(1024);
    char myhostname[32];
    ssize_t lockdatalen = 1024;
    ssize_t wroteamt;

129
130
    /* Run things which might fail first before we try and blow away the
     * old state. */
131
132
    myuid = geteuid();
    if ((mypwuid = getpwuid(myuid)) == NULL) {
133
	statusbar(_("Couldn't determine my identity for lock file (getpwuid() failed)"));
Benno Schulenberg's avatar
Benno Schulenberg committed
134
	goto free_and_fail;
135
136
137
138
    }
    mypid = getpid();

    if (gethostname(myhostname, 31) < 0) {
139
140
141
142
143
144
	if (errno == ENAMETOOLONG)
	    myhostname[31] = '\0';
	else {
	    statusbar(_("Couldn't determine hostname for lock file: %s"), strerror(errno));
	    goto free_and_fail;
	}
145
146
    }

147
148
149
    /* Check if the lock exists before we try to delete it...*/
    if (stat(lockfilename, &fileinfo) != -1)
	if (delete_lockfile(lockfilename) < 0)
Benno Schulenberg's avatar
Benno Schulenberg committed
150
	    goto free_and_fail;
151
152

    if (ISSET(INSECURE_BACKUP))
153
	cflags = O_WRONLY | O_CREAT | O_APPEND;
154
    else
155
	cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;
156
157

    fd = open(lockfilename, cflags,
158
		S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
159

160
    /* Maybe we just don't have write access.  Print an error message
161
     * and continue. */
162
    if (fd < 0) {
163
	statusbar(_("Error writing lock file %s: %s"), lockfilename,
164
		    strerror(errno));
Benno Schulenberg's avatar
Benno Schulenberg committed
165
	free(lockdata);
166
	return 0;
167
    }
168

169
170
    /* Now we've got a safe file stream.  If the previous open() call
     * failed, this will return NULL. */
171
172
173
    filestream = fdopen(fd, "wb");

    if (fd < 0 || filestream == NULL) {
174
	statusbar(_("Error writing lock file %s: %s"), lockfilename,
175
		    strerror(errno));
Benno Schulenberg's avatar
Benno Schulenberg committed
176
	goto free_and_fail;
177
178
    }

179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
    /* Okay, so at the moment we're following this state for how to
     * store the lock data:
     *
     * byte 0        - 0x62
     * byte 1        - 0x30
     * bytes 2-12    - program name which created the lock
     * bytes 24,25   - little endian store of creator program's PID
     *                 (b24 = 256^0 column, b25 = 256^1 column)
     * bytes 28-44   - username of who created the lock
     * bytes 68-100  - hostname of where the lock was created
     * bytes 108-876 - filename the lock is for
     * byte 1007     - 0x55 if file is modified
     *
     * Looks like VIM also stores undo state in this file, so we're
     * gonna have to figure out how to slap a 'OMG don't use recover on
     * our lockfile' message in here...
     *
     * This is likely very wrong, so this is a WIP. */
197
    memset(lockdata, 0, lockdatalen);
198
199
200
201
    lockdata[0] = 0x62;
    lockdata[1] = 0x30;
    lockdata[24] = mypid % 256;
    lockdata[25] = mypid / 256;
202
    snprintf(&lockdata[2], 11, "nano %s", VERSION);
203
204
205
    strncpy(&lockdata[28], mypwuid->pw_name, 16);
    strncpy(&lockdata[68], myhostname, 31);
    strncpy(&lockdata[108], origfilename, 768);
206
    if (modified == TRUE)
207
	lockdata[1007] = 0x55;
208
209
210

    wroteamt = fwrite(lockdata, sizeof(char), lockdatalen, filestream);
    if (wroteamt < lockdatalen) {
211
212
	statusbar(_("Error writing lock file %s: %s"),
		lockfilename, ferror(filestream));
Benno Schulenberg's avatar
Benno Schulenberg committed
213
	goto free_and_fail;
214
215
216
    }

#ifdef DEBUG
217
    fprintf(stderr, "In write_lockfile(), write successful (wrote %lu bytes)\n", (unsigned long)wroteamt);
218
#endif
219
220

    if (fclose(filestream) == EOF) {
221
222
	statusbar(_("Error writing lock file %s: %s"),
		lockfilename, strerror(errno));
Benno Schulenberg's avatar
Benno Schulenberg committed
223
	goto free_and_fail;
224
225
    }

226
    openfile->lock_filename = (char *) lockfilename;
227

Benno Schulenberg's avatar
Benno Schulenberg committed
228
    free(lockdata);
229
    return 1;
Benno Schulenberg's avatar
Benno Schulenberg committed
230
231
232
233

  free_and_fail:
    free(lockdata);
    return -1;
234
235
}

236
237
/* Less exciting, delete the lockfile.  Return -1 if unsuccessful and
 * complain on the statusbar, 1 otherwise. */
238
239
240
int delete_lockfile(const char *lockfilename)
{
    if (unlink(lockfilename) < 0 && errno != ENOENT) {
241
242
	statusbar(_("Error deleting lock file %s: %s"), lockfilename,
		  strerror(errno));
243
	return -1;
244
245
246
247
    }
    return 1;
}

248
249
250
251
/* Deal with lockfiles.  Return -1 on refusing to override the lockfile,
 * and 1 on successfully creating it; 0 means we were not successful in
 * creating the lockfile but we should continue to load the file and
 * complain to the user. */
252
253
int do_lockfile(const char *filename)
{
254
255
    char *namecopy1 = (char *) mallocstrcpy(NULL, filename);
    char *namecopy2 = (char *) mallocstrcpy(NULL, filename);
Benno Schulenberg's avatar
Benno Schulenberg committed
256
    size_t locknamesize = strlen(filename) + strlen(locking_prefix)
257
		+ strlen(locking_suffix) + 3;
Benno Schulenberg's avatar
Benno Schulenberg committed
258
    char *lockfilename = charalloc(locknamesize);
259
    char *lockfiledir = NULL;
260
    static char lockprog[11], lockuser[17];
261
262
263
    struct stat fileinfo;
    int lockfd, lockpid;

264
265
266
267
    snprintf(lockfilename, locknamesize, "%s/%s%s%s", dirname(namecopy1),
		locking_prefix, basename(namecopy2), locking_suffix);
    free(namecopy1);
    free(namecopy2);
268
269
#ifdef DEBUG
    fprintf(stderr, "lock file name is %s\n", lockfilename);
270
#endif
271
    if (stat(lockfilename, &fileinfo) != -1) {
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
	ssize_t readtot = 0;
	ssize_t readamt = 0;
	char *lockbuf = charalloc(8192);
	char *promptstr = charalloc(128);
	int ans;
	if ((lockfd = open(lockfilename, O_RDONLY)) < 0) {
	    statusbar(_("Error opening lock file %s: %s"),
			lockfilename, strerror(errno));
	    return -1;
	}
	do {
	    readamt = read(lockfd, &lockbuf[readtot], BUFSIZ);
	    readtot += readamt;
	} while (readtot < 8192 && readamt > 0);

	if (readtot < 48) {
	    statusbar(_("Error reading lock file %s: Not enough data read"),
			lockfilename);
	    return -1;
	}
	strncpy(lockprog, &lockbuf[2], 10);
	lockpid = (unsigned char)lockbuf[25] * 256 + (unsigned char)lockbuf[24];
	strncpy(lockuser, &lockbuf[28], 16);
295
#ifdef DEBUG
296
297
298
	fprintf(stderr, "lockpid = %d\n", lockpid);
	fprintf(stderr, "program name which created this lock file should be %s\n", lockprog);
	fprintf(stderr, "user which created this lock file should be %s\n", lockuser);
299
#endif
300
301
	/* TRANSLATORS: The second %s is the name of the user, the third that of the editor. */
	sprintf(promptstr, _("File %s is being edited (by %s with %s, PID %d); continue?"),
302
303
304
305
306
307
			filename, lockuser, lockprog, lockpid);
	ans = do_yesno_prompt(FALSE, promptstr);
	if (ans < 1) {
	    blank_statusbar();
	    return -1;
	}
308
    } else {
309
310
311
	lockfiledir = mallocstrcpy(NULL, lockfilename);
	lockfiledir = dirname(lockfiledir);
	if (stat(lockfiledir, &fileinfo) == -1) {
312
	    statusbar(_("Error writing lock file: Directory \'%s\' doesn't exist"),
313
		lockfiledir);
314
	    free(lockfiledir);
315
	    return 0;
316
	}
317
	free(lockfiledir);
318
319
320
321
    }

    return write_lockfile(lockfilename, filename, FALSE);
}
322
#endif /* !NANO_TINY */
323

324
325
/* 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. */
326
bool open_buffer(const char *filename, bool undoable)
327
{
328
    bool quiet = FALSE;
329
    bool new_buffer = (openfile == NULL
330
#ifndef DISABLE_MULTIBUFFER
331
	 || ISSET(MULTIBUFFER)
332
#endif
333
334
335
336
337
338
	);
	/* 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. */
339

340
341
    assert(filename != NULL);

342
343
344
345
#ifndef DISABLE_OPERATINGDIR
    if (check_operating_dir(filename, FALSE)) {
	statusbar(_("Can't insert file from outside of %s"),
		operating_dir);
346
	return FALSE;
347
348
    }
#endif
349

350
351
352
353
354
355
356
357
358
359
360
    /* When the specified filename is not empty, and the thing exists,
     * verify that it is a normal file. */
    if (strcmp(filename, "") != 0) {
	struct stat fileinfo;

	if (stat(filename, &fileinfo) == 0 && !S_ISREG(fileinfo.st_mode)) {
	    if (S_ISDIR(fileinfo.st_mode))
		statusbar(_("\"%s\" is a directory"), filename);
	    else
		statusbar(_("\"%s\" is not a normal file"), filename);
	    beep();
361
	    return FALSE;
362
363
364
	}
    }

365
366
    /* If we're going to load into a new buffer, first create the new
     * buffer and lock the corresponding file. */
367
    if (new_buffer) {
368
	make_new_buffer();
369

370
371
372
373
#ifndef NANO_TINY
	if (ISSET(LOCKING) && filename[0] != '\0') {
	    int lockstatus = do_lockfile(filename);
	    if (lockstatus < 0) {
374
#ifndef DISABLE_MULTIBUFFER
375
		if (openfile->next) {
376
		    close_buffer(TRUE);
377
		    return FALSE;
378
379
380
381
		}
#endif
	    } else if (lockstatus == 0) {
		quiet = TRUE;
382
	    }
383
	}
384
385
386
#endif
    }

387
388
389
    /* If the filename isn't blank, and we are not in NOREAD_MODE,
     * open the file.  Otherwise, treat it as a new file. */
    rc = (filename[0] != '\0' && !ISSET(NOREAD_MODE)) ?
390
		open_file(filename, new_buffer, quiet, &f) : -2;
391

392
393
    /* If we have a file, and we're loading into a new buffer, update
     * the filename. */
394
395
    if (rc != -1 && new_buffer)
	openfile->filename = mallocstrcpy(openfile->filename, filename);
396

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
397
398
    /* If we have a non-new file, read it in.  Then, if the buffer has
     * no stat, update the stat, if applicable. */
399
    if (rc > 0) {
400
	read_file(f, rc, filename, undoable, new_buffer);
401
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
402
403
404
405
406
	if (openfile->current_stat == NULL) {
	    openfile->current_stat =
		(struct stat *)nmalloc(sizeof(struct stat));
	    stat(filename, openfile->current_stat);
	}
407
#endif
408
409
    }

410
    /* If we have a file, and we're loading into a new buffer, move back
411
412
     * to the beginning of the first line of the buffer. */
    if (rc != -1 && new_buffer) {
413
	openfile->current = openfile->fileage;
414
415
416
	openfile->current_x = 0;
	openfile->placewewant = 0;
    }
417

418
#ifndef DISABLE_COLOR
419
420
    /* If we're loading into a new buffer, update the colors to account
     * for it, if applicable. */
421
422
423
    if (new_buffer)
	color_update();
#endif
424
    return TRUE;
425
}
426

427
#ifndef DISABLE_SPELLER
428
429
/* Blow away the text of the current buffer, and then open and read
 * the specified file into its place. */
430
431
432
void replace_buffer(const char *filename)
{
    FILE *f;
433
    int descriptor;
434

435
    assert(filename != NULL && filename[0] != '\0');
436

437
438
    /* Open the file quietly. */
    descriptor = open_file(filename, TRUE, FALSE, &f);
439
440
441
442
443

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

444
445
446
    /* If opening the file succeeded, read it in. */
    if (descriptor > 0)
	read_file(f, descriptor, filename, FALSE, TRUE);
447
448
449

    /* Put current at a place that is certain to exist. */
    openfile->current = openfile->fileage;
450
451
452
}
#endif /* !DISABLE_SPELLER */

453
/* Update the screen to account for the current buffer. */
454
void display_buffer(void)
455
{
456
    /* Update the titlebar, since the filename may have changed. */
457
    titlebar(NULL);
458

459
#ifndef DISABLE_COLOR
460
    /* Make sure we're using the buffer's associated colors. */
461
    color_init();
462
463
464
465
466
467

    /* If there are multiline coloring regexes, and there is no
     * multiline cache data yet, precalculate it now. */
    if (openfile->syntax && openfile->syntax->nmultis > 0 &&
		openfile->fileage->multidata == NULL)
	precalc_multicolorinfo();
468
469
470
#endif

    /* Update the edit window. */
471
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
472
473
}

474
#ifndef DISABLE_MULTIBUFFER
475
476
477
/* Switch to a neighbouring file buffer; to the next if to_next is TRUE;
 * otherwise, to the previous one. */
void switch_to_prevnext_buffer(bool to_next, bool quiet)
Chris Allegretta's avatar
Chris Allegretta committed
478
{
479
    assert(openfile != NULL);
480

481
    /* If only one file buffer is open, say so and get out. */
482
    if (openfile == openfile->next) {
483
	if (!quiet)
484
	    statusbar(_("No more open file buffers"));
485
	return;
Chris Allegretta's avatar
Chris Allegretta committed
486
    }
487

488
489
    /* Switch to the next or previous file buffer. */
    openfile = to_next ? openfile->next : openfile->prev;
490
491

#ifdef DEBUG
492
    fprintf(stderr, "filename is %s\n", openfile->filename);
493
494
#endif

495
    /* Update the screen to account for the current buffer. */
496
    display_buffer();
497

498
    /* Indicate the switch on the statusbar. */
499
    if (!quiet)
500
	statusbar(_("Switched to %s"),
501
502
		((openfile->filename[0] == '\0') ?
		_("New Buffer") : openfile->filename));
503
504

#ifdef DEBUG
505
    dump_filestruct(openfile->current);
506
#endif
507
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
508
509
}

510
/* Switch to the previous entry in the openfile filebuffer. */
511
void switch_to_prev_buffer_void(void)
512
{
513
    switch_to_prevnext_buffer(FALSE, FALSE);
514
}
515

516
/* Switch to the next entry in the openfile filebuffer. */
517
void switch_to_next_buffer_void(void)
518
{
519
    switch_to_prevnext_buffer(TRUE, FALSE);
520
}
521

522
/* Delete an entry from the openfile filebuffer, and switch to the one
523
 * after it.  Return TRUE on success, or FALSE if there are no more open
524
525
526
527
 * file buffers.
 * quiet - should we print messages switching bufers
 */
bool close_buffer(bool quiet)
528
{
529
    assert(openfile != NULL);
530

531
    /* If only one file buffer is open, get out. */
532
    if (openfile == openfile->next)
533
	return FALSE;
534

535
#ifndef DISABLE_HISTORIES
536
    update_poshistory(openfile->filename, openfile->current->lineno, xplustabs() + 1);
537
#endif
538

539
    /* Switch to the next file buffer. */
540
    switch_to_prevnext_buffer(TRUE, quiet);
541

542
    /* Close the file buffer we had open before. */
543
    unlink_opennode(openfile->prev);
544

545
546
547
    /* If only one buffer is open now, show Exit in the help lines. */
    if (openfile == openfile->next)
	exitfunc->desc = exit_tag;
548

549
    return TRUE;
550
}
551
#endif /* !DISABLE_MULTIBUFFER */
552

553
554
555
556
557
/* 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. */
558
559
560
561
562
563
564
565
566
567
568
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;

569
    assert(filename != NULL);
570
571
572
573

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

574
575
    /* Okay, if we can't stat the absolute path due to some component's
     * permissions, just try the relative one. */
576
577
578
    if (full_filename == NULL ||
		(stat(full_filename, &fileinfo) == -1 && stat(filename, &fileinfo2) != -1))
	full_filename = mallocstrcpy(NULL, filename);
579
580

    if ((fd = open(full_filename, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR |
581
582
		S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) == -1 ||
		(f = fdopen(fd, "a")) == NULL)
583
584
	ans = FALSE;
    else
585
	fclose(f);
586
587
588
589
590
591
    close(fd);

    free(full_filename);
    return ans;
}

592
593
594
/* Make a new line of text from the given buf, which is of length buf_len.
 * Then attach this line after prevnode. */
filestruct *read_line(char *buf, size_t buf_len, filestruct *prevnode)
595
{
596
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
597

598
    /* Convert nulls to newlines.  buf_len is the string's real length. */
599
    unsunder(buf, buf_len);
600

601
    assert(openfile->fileage != NULL && strlen(buf) == buf_len);
602

603
    fileptr->data = mallocstrcpy(NULL, buf);
604

605
#ifndef NANO_TINY
606
607
608
609
    /* 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';
610
#endif
611

612
#ifndef DISABLE_COLOR
613
    fileptr->multidata = NULL;
614
615
#endif

616
617
618
    fileptr->prev = prevnode;

    if (prevnode == NULL) {
619
	/* Special case: we're inserting into the first line. */
620
	fileptr->next = openfile->fileage;
621
	openfile->fileage = fileptr;
622
	fileptr->lineno = 1;
623
	/* Make sure that our edit window stays on the first line. */
624
	openfile->edittop = fileptr;
625
    } else {
626
	prevnode->next = fileptr;
627
628
	fileptr->next = NULL;
	fileptr->lineno = prevnode->lineno + 1;
629
630
    }

631
632
    return fileptr;
}
633

634
/* Read an open file into the current buffer.  f should be set to the
635
 * open file, and filename should be set to the name of the file.
636
637
638
 * undoable means do we want to create undo records to try and undo
 * this.  Will also attempt to check file writability if fd > 0 and
 * checkwritable == TRUE. */
639
void read_file(FILE *f, int fd, const char *filename, bool undoable, bool checkwritable)
640
641
642
643
644
645
646
647
648
649
650
651
652
{
    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. */
653
654
    filestruct *fileptr = openfile->current->prev;
	/* The line after which to start inserting. */
655
656
657
    int input_int;
	/* The current value we read from the file, whether an input
	 * character or EOF. */
658
659
    bool writable = TRUE;
	/* Is the file writable (if we care) */
660
#ifndef NANO_TINY
661
662
    int format = 0;
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
663
#endif
664

665
666
    assert(openfile->fileage != NULL && openfile->current != NULL);

667
668
    buf = charalloc(bufx);
    buf[0] = '\0';
669

670
671
672
673
674
#ifndef NANO_TINY
    if (undoable)
	add_undo(INSERT);
#endif

675
    /* Read the entire file into the filestruct. */
676
677
678
679
680
681
    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') {
682
#ifndef NANO_TINY
683
684
685
686
687
	    /* 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) &&
688
			i > 0 && buf[i - 1] == '\r') {
689
690
691
		if (format == 0 || format == 2)
		    format++;
	    }
692
#endif
693

694
	    /* Read in the line properly. */
695
	    fileptr = read_line(buf, len, fileptr);
696

697
	    /* Reset the line length in preparation for the next line. */
698
	    len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
699

700
701
702
	    num_lines++;
	    buf[0] = '\0';
	    i = 0;
703
#ifndef NANO_TINY
704
705
706
707
708
	/* 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') {
709
710
711
712
713
	    /* 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;
714

715
	    /* Read in the line properly. */
716
	    fileptr = read_line(buf, len, fileptr);
717

718
719
720
721
	    /* 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
722

723
724
725
726
	    num_lines++;
	    buf[0] = input;
	    buf[1] = '\0';
	    i = 1;
727
#endif
728
729
730
731
	} else {
	    /* Calculate the total length of the line.  It might have
	     * nulls in it, so we can't just use strlen() here. */
	    len++;
732

733
734
735
736
737
738
739
740
	    /* 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);
741
	    }
742
743
744
745
746
747
748
749
750
751
752

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

    /* Perhaps this could use some better handling. */
    if (ferror(f))
	nperror(filename);
    fclose(f);
753
    if (fd > 0 && checkwritable) {
754
	close(fd);
755
756
	writable = is_file_writable(filename);
    }
757

758
#ifndef NANO_TINY
759
760
761
762
763
764
765
766
    /* 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';
    }
767
#endif
768

769
770
    /* Did we not get a newline and still have stuff to do? */
    if (len > 0) {
771
#ifndef NANO_TINY
772
773
774
775
776
777
778
	/* 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;
779
780
#endif

781
	/* Read in the last line properly. */
782
	fileptr = read_line(buf, len, fileptr);
783
784
	num_lines++;
    }
785

786
    free(buf);
787

788
789
    /* Attach the file we got to the filestruct.  If we got a file of
     * zero bytes, don't do anything. */
790
    if (num_lines > 0) {
791
792
793
794
795
796
797
798
799
800
801
802
	/* 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
803
804
	    /* Tack the text at fileptr onto the beginning of the text
	     * at current. */
805
806
807
808
	    openfile->current->data = charealloc(openfile->current->data,
						len + current_len + 1);
	    charmove(openfile->current->data + len, openfile->current->data,
			current_len + 1);
809
810
811
812
813
814
815
816
817
818
	    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
819
	    /* Move fileptr back one line and blow away the old fileptr,
820
821
	     * since its text has been saved. */
	    fileptr = fileptr->prev;
822
823
	    if (fileptr != NULL)
		free(fileptr->next);
824
825
826
827
828
829
830
831
	}

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

832
	/* Renumber, starting with the last line of the file we inserted. */
833
	renumber(openfile->current);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
834
    }
835

836
    openfile->totsize += get_totsize(openfile->fileage, openfile->filebot);
837

838
    /* If the NO_NEWLINES flag isn't set, and text has been added to
839
     * the magicline (i.e. a file that doesn't end in a newline has been
840
841
842
     * 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') {
843
844
845
846
847
848
849
850
851
	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();

852
#ifndef NANO_TINY
853
854
855
    if (undoable)
	update_undo(INSERT);

856
857
858
    if (format == 3) {
	if (writable)
	    statusbar(
859
860
861
		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);
862
863
864
865
866
867
	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) {
868
	openfile->fmt = MAC_FILE;
869
870
	if (writable)
	    statusbar(P_("Read %lu line (Converted from Mac format)",
871
872
		"Read %lu lines (Converted from Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
873
874
875
876
	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);
877
    } else if (format == 1) {
878
	openfile->fmt = DOS_FILE;
879
880
	if (writable)
	    statusbar(P_("Read %lu line (Converted from DOS format)",
881
882
		"Read %lu lines (Converted from DOS format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
883
884
885
886
	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);
887
    } else
888
#endif
889
890
891
892
	if (writable)
	    statusbar(P_("Read %lu line", "Read %lu lines",
		(unsigned long)num_lines), (unsigned long)num_lines);
	else
893
	    statusbar(P_("Read %lu line (Warning: No write permission)",
894
		"Read %lu lines (Warning: No write permission)",
895
		(unsigned long)num_lines), (unsigned long)num_lines);
896

897
#ifndef NANO_TINY
898
899
    if (ISSET(MAKE_IT_UNIX))
	openfile->fmt = NIX_FILE;
900
#endif
901
}
Chris Allegretta's avatar
Chris Allegretta committed
902

903
904
905
906
/* 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".
 *
907
 * Return -2 if we say "New File", -1 if the file isn't opened, and the
908
 * fd opened otherwise.  The file might still have an error while reading
909
 * with a 0 return value.  *f is set to the opened file. */
910
int open_file(const char *filename, bool newfie, bool quiet, FILE **f)
911
{
912
    struct stat fileinfo, fileinfo2;
913
    int fd;
914
    char *full_filename;
915

916
    assert(filename != NULL && f != NULL);
917

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

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

    if (stat(full_filename, &fileinfo) == -1) {
928
929
930
	/* All cases below return. */
	free(full_filename);

931
932
	/* Well, maybe we can open the file even if the OS says it's
	 * not there. */
933
	if ((fd = open(filename, O_RDONLY)) != -1) {
934
935
	    if (!quiet)
		statusbar(_("Reading File"));
936
	    return fd;
937
938
	}

939
	if (newfie) {
940
941
	    if (!quiet)
		statusbar(_("New File"));
942
943
944
	    return -2;
	}
	statusbar(_("\"%s\" not found"), filename);
945
	beep();
946
947
	return -1;
    } else if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
948
		S_ISBLK(fileinfo.st_mode)) {
949
950
	free(full_filename);

951
952
	/* Don't open directories, character files, or block files.
	 * Sorry, /dev/sndstat! */
953
954
	statusbar(S_ISDIR(fileinfo.st_mode) ?
		_("\"%s\" is a directory") :
955
		_("\"%s\" is a device file"), filename);
956
	beep();
957
	return -1;
958
    } else if ((fd = open(full_filename, O_RDONLY)) == -1) {
959
	free(full_filename);
960
	statusbar(_("Error reading %s: %s"), filename, strerror(errno));
961
	beep();
962
	return -1;
963
    } else {
964
	/* The file is A-OK.  Open it. */
965
	*f = fdopen(fd, "rb");
966

967
	if (*f == NULL) {
968
	    statusbar(_("Error reading %s: %s"), filename, strerror(errno));
969
	    beep();
970
971
972
	    close(fd);
	} else
	    statusbar(_("Reading File"));
973
    }
974

975
976
    free(full_filename);

977
978
979
    return fd;
}

980
981
982
983
984
/* 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)
985
{
986
987
    unsigned long i = 0;
    char *buf;
988
    size_t wholenamelen;
989

990
    assert(name != NULL && suffix != NULL);
991

992
    wholenamelen = strlen(name) + strlen(suffix);
993

994
995
996
    /* Reserve space for: the name plus the suffix plus a dot plus
     * possibly five digits plus a null byte. */
    buf = charalloc(wholenamelen + 7);
997
    sprintf(buf, "%s%s", name, suffix);
998

999
1000
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
1001

1002
1003
	if (stat(buf, &fs) == -1)
	    return buf;
1004
1005
1006

	/* Limit the number of backup files to a hundred thousand. */
	if (++i == 100000)
1007
	    break;
1008

1009
	sprintf(buf + wholenamelen, ".%lu", i);
1010
    }
Chris Allegretta's avatar
Chris Allegretta committed
1011

1012
1013
1014
    /* We get here only if there is no possible save file.  Blank out
     * the filename to indicate this. */
    null_at(&buf, 0);
1015

1016
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
1017
1018
}

1019
1020
1021
/* 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. */
1022
void do_insertfile(
1023
#ifndef NANO_TINY
1024
1025
1026
1027
1028
1029
1030
1031
1032
	bool execute
#else
	void
#endif
	)
{
    int i;
    const char *msg;
    char *ans = mallocstrcpy(NULL, "");
1033
	/* The last answer the user typed at the statusbar prompt. */
1034
    filestruct *edittop_save = openfile->edittop;
1035
    ssize_t was_current_lineno = openfile->current->lineno;
1036
1037
    size_t was_current_x = openfile->current_x;
    ssize_t was_current_y = openfile->current_y;
1038
    bool edittop_inside = FALSE;
1039
#ifndef NANO_TINY
1040
    bool right_side_up = FALSE, single_line = FALSE;
1041
#endif
1042

1043
    while (TRUE) {
1044
#ifndef NANO_TINY
1045
	if (execute) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1046
	    msg =
1047
#ifndef DISABLE_MULTIBUFFER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1048
		ISSET(MULTIBUFFER) ?
1049
		_("Command to execute in new buffer [from %s] ") :
1050
#endif
1051
		_("Command to execute [from %s] ");
1052
1053
1054
	} else
#endif /* NANO_TINY */
	{
1055
	    msg =
1056
#ifndef DISABLE_MULTIBUFFER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1057
		ISSET(MULTIBUFFER) ?
1058
		_("File to insert into new buffer [from %s] ") :
1059
#endif
1060
		_("File to insert [from %s] ");
1061
	}
1062

1063
	i = do_prompt(TRUE,
1064
1065
1066
#ifndef DISABLE_TABCOMP
		TRUE,
#endif
1067
#ifndef NANO_TINY
1068
		execute ? MEXTCMD :
1069
#endif
1070
		MINSERTFILE, ans,
1071
#ifndef DISABLE_HISTORIES
1072
		NULL,
1073
#endif
1074
		edit_refresh, msg,
1075
#ifndef DISABLE_OPERATINGDIR
1076
1077
		operating_dir != NULL && strcmp(operating_dir,
		".") != 0 ? operating_dir :
1078
1079
#endif
		"./");
1080

1081
	/* If we're in multibuffer mode and the filename or command is
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1082
	 * blank, open a new buffer instead of canceling.  If the
1083
1084
	 * 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
1085
	if (i == -1 || ((i == -2 || *answer == '\n')
1086
#ifndef DISABLE_MULTIBUFFER
1087
1088
1089
1090
1091
1092
		&& !ISSET(MULTIBUFFER)
#endif
		)) {
	    statusbar(_("Cancelled"));
	    break;
	} else {
1093
	    size_t pww_save = openfile->placewewant;
1094
#if !defined(NANO_TINY) || !defined(DISABLE_BROWSER)
1095
	    functionptrtype func = func_from_key(&i);
1096
#endif
1097
	    ans = mallocstrcpy(ans, answer);
1098

1099
#ifndef NANO_TINY
1100
#ifndef DISABLE_MULTIBUFFER
1101
	    if (func == new_buffer_void) {
1102
1103
1104
		/* Don't allow toggling if we're in view mode. */
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
1105
1106
		else
		    beep();
1107
		continue;
1108
	    }
1109
#endif
1110
	    if (func == flip_execute_void) {
1111
1112
1113
		execute = !execute;
		continue;
	    }
1114
#endif /* !NANO_TINY */
1115

1116
#ifndef DISABLE_BROWSER
1117
	    if (func == to_files_void) {
1118
		char *tmp = do_browse_from(answer);
1119

1120
1121
		if (tmp == NULL)
		    continue;
1122

1123
		/* We have a file now.  Indicate this. */
1124
1125
		free(answer);
		answer = tmp;
1126

1127
1128
1129
		i = 0;
	    }
#endif
1130

1131
	    /* If we don't have a file yet, go back to the statusbar prompt. */
1132
	    if (i != 0
1133
#ifndef DISABLE_MULTIBUFFER
1134
1135
1136
1137
		&& (i != -2 || !ISSET(MULTIBUFFER))
#endif
		)
		continue;
1138

1139
#ifndef NANO_TINY
1140
1141
1142
1143
1144
	    /* 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;
1145

1146
		mark_order((const filestruct **)&top, &top_x,
1147
1148
1149
			(const filestruct **)&bot, &bot_x,
			&right_side_up);

1150
1151
		single_line = (top == bot);
	    }
1152
1153
#endif

1154
#ifndef DISABLE_MULTIBUFFER
1155
1156
1157
1158
	    if (!ISSET(MULTIBUFFER)) {
#endif
		/* If we're not inserting into a new buffer, partition
		 * the filestruct so that it contains no text and hence
1159
		 * looks like a new buffer, and keep track of whether
1160
		 * the top of the edit window is inside the partition. */
1161
1162
1163
		filepart = partition_filestruct(openfile->current,
			openfile->current_x, openfile->current,
			openfile->current_x);
1164
		edittop_inside = (openfile->edittop == openfile->fileage);
1165
#ifndef DISABLE_MULTIBUFFER
1166
1167
	    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1168

1169
	    /* Convert newlines to nulls in the given filename. */
1170
	    sunder(answer);
1171
	    align(&answer);
1172

1173
#ifndef NANO_TINY
1174
	    if (execute) {
1175
#ifndef DISABLE_MULTIBUFFER
1176
		if (ISSET(MULTIBUFFER))
1177
		    /* Open a blank buffer. */
1178
		    open_buffer("", FALSE);
1179
1180
1181
#endif

		/* Save the command's output in the current buffer. */
1182
		execute_command(answer);
1183

1184
#ifndef DISABLE_MULTIBUFFER
1185
1186
1187
1188
1189
1190
1191
		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;
		}
1192
#endif
1193
	    } else
1194
#endif /* !NANO_TINY */
1195
	    {
1196
1197
		/* Make sure the path to the file specified in answer is
		 * tilde-expanded. */
1198
		answer = mallocstrassn(answer, real_dir_from_tilde(answer));
1199

1200
		/* Save the file specified in answer in the current buffer. */
1201
		open_buffer(answer, TRUE);
1202
	    }
1203

1204
#if !defined(DISABLE_MULTIBUFFER) && !defined(DISABLE_HISTORIES)
1205
	    if (ISSET(MULTIBUFFER)) {
1206
		/* Update the screen to account for the current buffer. */
1207
		display_buffer();
1208
1209

		ssize_t savedposline, savedposcol;
1210
1211
1212
1213
1214
		if (ISSET(POS_HISTORY) &&
#ifndef NANO_TINY
			!execute &&
#endif
			check_poshistory(answer, &savedposline, &savedposcol))
1215
		    do_gotolinecolumn(savedposline, savedposcol, FALSE, FALSE);
1216
	    } else
1217
#endif /* !DISABLE_MULTIBUFFER && !DISABLE_HISTORIES */
1218
	    {
1219
		filestruct *top_save = openfile->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
1220

1221
1222
1223
		/* 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
1224
		if (edittop_inside)
1225
		    edittop_save = openfile->fileage;
1226
1227

		/* Update the current x-coordinate to account for the
1228
		 * number of characters inserted on the current line.
1229
1230
1231
		 * If the mark was positioned after the cursor and on the
		 * same line, adjust the mark's coordinates to compensate
		 * for the change in this line. */
1232
		openfile->current_x = strlen(openfile->filebot->data);
1233
1234
1235
1236
		if (openfile->fileage == openfile->filebot) {
#ifndef NANO_TINY
		    if (openfile->mark_set) {
			openfile->mark_begin = openfile->current;
1237
			if (!right_side_up)
1238
			    openfile->mark_begin_x += openfile->current_x;
1239
1240
		    }
#endif
1241
		    openfile->current_x += was_current_x;
1242
		}
1243
#ifndef NANO_TINY
1244
1245
1246
1247
		else if (openfile->mark_set) {
		    if (!right_side_up) {
			if (single_line) {
			    openfile->mark_begin = openfile->current;
1248
			    openfile->mark_begin_x -= was_current_x;
1249
			} else
1250
			    openfile->mark_begin_x -= openfile->current_x;
1251
1252
		    }
		}
1253
#endif
1254
1255
1256

		/* Update the current y-coordinate to account for the
		 * number of lines inserted. */
1257
		openfile->current_y += was_current_y;
1258

1259
1260
1261
1262
		/* 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. */
1263
		unpartition_filestruct(&filepart);
1264

1265
1266
1267
		/* Renumber starting with the beginning line of the old
		 * partition. */
		renumber(top_save);
1268

1269
		/* Restore the old edittop. */
1270
		openfile->edittop = edittop_save;
1271

1272
1273
1274
		/* Restore the old place we want. */
		openfile->placewewant = pww_save;

1275
1276
		/* Mark the file as modified if it changed. */
		if (openfile->current->lineno != was_current_lineno ||
1277
			openfile->current_x != was_current_x)
1278
		    set_modified();
1279

1280
1281
		/* Update the screen. */
		edit_refresh();
1282
	    }
1283

1284
1285
1286
1287
	    break;
	}
    }
    free(ans);
1288
1289
}

1290
1291
1292
/* 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. */
1293
void do_insertfile_void(void)
1294
{
1295
    if (ISSET(RESTRICTED)) {
1296
	show_restricted_warning();
1297
1298
1299
	return;
    }

1300
#ifndef DISABLE_MULTIBUFFER
1301
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
1302
	statusbar(_("Key invalid in non-multibuffer mode"));
1303
1304
1305
    else
#endif
	do_insertfile(
1306
#ifndef NANO_TINY
1307
1308
1309
		FALSE
#endif
		);
1310
1311
1312
1313

    display_main_list();
}

1314
/* When passed "[relative path]" or "[relative path][filename]" in
1315
 * origpath, return "[full path]" or "[full path][filename]" on success,
1316
1317
1318
1319
 * 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. */
1320
char *get_full_path(const char *origpath)
1321
{
1322
1323
1324
1325
    struct stat fileinfo;
    char *d_here, *d_there, *d_there_file = NULL;
    const char *last_slash;
    bool path_only;
1326

1327
    if (origpath == NULL)
1328
	return NULL;
1329

1330
    /* Get the current directory.  If it doesn't exist, back up and try
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1331
1332
     * again until we get a directory that does, and use that as the
     * current directory. */
1333
1334
    d_here = charalloc(PATH_MAX + 1);
    d_here = getcwd(d_here, PATH_MAX + 1);
1335

1336
    while (d_here == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1337
1338
1339
	if (chdir("..") == -1)
	    break;

1340
1341
1342
1343
1344
	d_here = getcwd(d_here, PATH_MAX + 1);
    }

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

1347
1348
	/* If the current directory isn't "/", tack a slash onto the end
	 * of it. */
1349
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1350
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1351
1352
	    strcat(d_here, "/");
	}
1353
1354
1355
    /* Otherwise, set d_here to "". */
    } else
	d_here = mallocstrcpy(NULL, "");
1356

1357
    d_there = real_dir_from_tilde(origpath);
1358

1359
1360
1361
    /* 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. */
1362
    path_only = (stat(d_there, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode));
1363

1364
1365
1366
    /* If path_only is TRUE, make sure d_there ends in a slash. */
    if (path_only) {
	size_t d_there_len = strlen(d_there);
1367

1368
1369
1370
	if (d_there[d_there_len - 1] != '/') {
	    d_there = charealloc(d_there, d_there_len + 2);
	    strcat(d_there, "/");
1371
	}
1372
    }
1373

1374
1375
    /* Search for the last slash in d_there. */
    last_slash = strrchr(d_there, '/');
1376

1377
1378
1379
1380
    /* 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);
1381

1382
1383
1384
1385
1386
1387
1388
1389
	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);

1390
	/* Remove the filename portion of the answer from d_there. */
1391
1392
	null_at(&d_there, last_slash - d_there + 1);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1393
	/* Go to the path specified in d_there. */
1394
1395
1396
	if (chdir(d_there) == -1) {
	    free(d_there);
	    d_there = NULL;
1397
	} else {
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
	    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()
1422
	     * error, since we can do nothing if we get one. */
1423
	    IGNORE_CALL_RESULT(chdir(d_here));
1424

1425
1426
	    /* Free d_here, since we're done using it. */
	    free(d_here);
1427
	}
1428
    }
1429

1430
1431
1432
1433
1434
1435
1436
    /* 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) +
1437
		strlen(d_there_file) + 1);
1438
1439
	strcat(d_there, d_there_file);
    }
1440

1441
    /* Free d_there_file, since we're done using it. */
1442
    free(d_there_file);
1443

1444
    return d_there;
1445
}
1446

1447
/* Return the full version of path, as returned by get_full_path().  On
1448
 * error, or if path doesn't reference a directory, or if the directory
1449
 * isn't writable, return NULL. */
1450
char *check_writable_directory(const char *path)
1451
{
1452
1453
    char *full_path = get_full_path(path);

1454
    /* If get_full_path() fails, return NULL. */
1455
    if (full_path == NULL)
1456
	return NULL;
1457

1458
    /* If we can't write to path or path isn't a directory, return NULL. */
1459
    if (access(full_path, W_OK) != 0 ||
1460
		full_path[strlen(full_path) - 1] != '/') {
1461
	free(full_path);
1462
	return NULL;
1463
    }
1464

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1465
    /* Otherwise, return the full path. */
1466
1467
1468
    return full_path;
}

1469
1470
/* This function calls mkstemp(($TMPDIR|P_tmpdir|/tmp/)"nano.XXXXXX").
 * On success, it returns the malloc()ed filename and corresponding FILE
1471
 * stream, opened in "r+b" mode.  On error, it returns NULL for the
1472
1473
 * filename and leaves the FILE stream unchanged. */
char *safe_tempfile(FILE **f)
1474
{
1475
    char *full_tempdir = NULL;
1476
1477
1478
    const char *tmpdir_env;
    int fd;
    mode_t original_umask = 0;
1479

1480
1481
    assert(f != NULL);

1482
1483
1484
    /* 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. */
1485
    tmpdir_env = getenv("TMPDIR");
1486
    if (tmpdir_env != NULL)
1487
	full_tempdir = check_writable_directory(tmpdir_env);
1488

1489
1490
    /* If $TMPDIR is unset, empty, or not a writable directory, and
     * full_tempdir is NULL, try P_tmpdir instead. */
1491
    if (full_tempdir == NULL)
1492
	full_tempdir = check_writable_directory(P_tmpdir);
1493

1494
1495
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
1496
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1497

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1498
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1499
    strcat(full_tempdir, "nano.XXXXXX");
1500

1501
1502
1503
1504
1505
1506
1507
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

    if (fd != -1)
	*f = fdopen(fd, "r+b");
1508
1509
1510
    else {
	free(full_tempdir);
	full_tempdir = NULL;
1511
    }
1512

1513
1514
    umask(original_umask);

1515
    return full_tempdir;
1516
}
1517
1518

#ifndef DISABLE_OPERATINGDIR
1519
1520
1521
1522
1523
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1524
    if (operating_dir == NULL)
1525
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1526

1527
1528
1529
    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
1530
     * inaccessible, unset operating_dir. */
1531
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1532
1533
1534
1535
1536
1537
1538
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1539
1540
1541
1542
1543
/* 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)
1544
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1545
1546
1547
1548
    /* 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. */
1549

1550
    char *fullpath;
1551
    bool retval = FALSE;
1552
    const char *whereami1, *whereami2 = NULL;
1553

1554
    /* If no operating directory is set, don't bother doing anything. */
1555
    if (operating_dir == NULL)
1556
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1557

1558
    assert(full_operating_dir != NULL);
1559
1560

    fullpath = get_full_path(currpath);
1561

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1562
1563
1564
    /* 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
1565
     * non-existent directory as being outside the operating directory,
1566
     * so we return FALSE.  If allow_tabcomp is TRUE, then currpath
1567
1568
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1569
    if (fullpath == NULL)
1570
	return allow_tabcomp;
1571
1572
1573
1574
1575

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

1576
    /* If both searches failed, we're outside the operating directory.
1577
     * Otherwise, check the search results.  If the full operating
1578
1579
1580
     * 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. */
1581
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1582
1583
	retval = TRUE;
    free(fullpath);
1584
1585

    /* Otherwise, we're still inside it. */
1586
    return retval;
1587
}
1588
1589
#endif

1590
#ifndef NANO_TINY
1591
1592
/* Although this sucks, it sucks less than having a single 'my system is
 * messed up and I'm blanket allowing insecure file writing operations'. */
1593
1594
1595
int prompt_failed_backupwrite(const char *filename)
{
    static int i;
1596
    static char *prevfile = NULL; /* What was the last file we were
1597
1598
				   * passed so we don't keep asking
				   * this?  Though maybe we should... */
1599
1600
    if (prevfile == NULL || strcmp(filename, prevfile)) {
	i = do_yesno_prompt(FALSE,
1601
		_("Failed to write backup file, continue saving? (Say N if unsure) "));
1602
1603
1604
1605
1606
	prevfile = mallocstrcpy(prevfile, filename);
    }
    return i;
}

1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
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;
    }
}
1628
#endif /* !NANO_TINY */
1629

1630
/* Read from inn, write to out.  We assume inn is opened for reading,
1631
1632
 * and out for writing.  We return 0 on success, -1 on read error, or -2
 * on write error. */
1633
1634
int copy_file(FILE *inn, FILE *out)
{
1635
    int retval = 0;
1636
    char buf[BUFSIZ];
1637
1638
    size_t charsread;

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

1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
    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
1652

1653
1654
1655
1656
    if (fclose(inn) == EOF)
	retval = -1;
    if (fclose(out) == EOF)
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1657

1658
1659
1660
    return retval;
}

1661
1662
/* 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
1663
 * ourselves.  If tmp is TRUE, we set the umask to disallow anyone else
1664
1665
 * 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
1666
 *
1667
 * tmp means we are writing a temporary file in a secure fashion.  We
1668
1669
1670
1671
 * 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
1672
1673
 * filename.  nonamechange is ignored if tmp is FALSE, we're appending,
 * or we're prepending.
1674
 *
1675
1676
 * Return TRUE on success or FALSE on error. */
bool write_file(const char *name, FILE *f_open, bool tmp, append_type
1677
	append, bool nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1678
{
1679
    bool retval = FALSE;
1680
	/* Instead of returning in this function, you should always
1681
	 * set retval and then goto cleanup_and_exit. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1682
    size_t lineswritten = 0;
1683
    const filestruct *fileptr = openfile->fileage;
1684
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1685
	/* The file descriptor we use. */
1686
    mode_t original_umask = 0;
1687
	/* Our umask, from when nano started. */
1688
#ifndef NANO_TINY
1689
    bool realexists;
1690
	/* The result of stat().  TRUE if the file exists, FALSE
1691
	 * otherwise.  If name is a link that points nowhere, realexists
1692
	 * is FALSE. */
1693
1694
    struct stat st;
	/* The status fields filled in by stat(). */
1695
#endif
1696
    bool anyexists;
1697
1698
	/* The result of lstat().  The same as realexists, unless name
	 * is a link. */
1699
1700
1701
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1702
	/* name after tilde expansion. */
1703
    FILE *f = NULL;
1704
1705
	/* The actual file, realname, we are writing to. */
    char *tempname = NULL;
1706
	/* The name of the temporary file we write to on prepend. */
Chris Allegretta's avatar
Chris Allegretta committed
1707

1708
    assert(name != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1709

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1710
    if (*name == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1711
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1712

1713
1714
1715
    if (f_open != NULL)
	f = f_open;

1716
1717
    if (!tmp)
	titlebar(NULL);
1718

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1719
    realname = real_dir_from_tilde(name);
1720

1721
#ifndef DISABLE_OPERATINGDIR
1722
    /* If we're writing a temporary file, we're probably going outside
1723
     * the operating directory, so skip the operating directory test. */
1724
    if (!tmp && check_operating_dir(realname, FALSE)) {
1725
1726
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1727
1728
1729
    }
#endif

1730
    anyexists = (lstat(realname, &lst) != -1);
1731

1732
1733
    /* If the temp file exists and isn't already open, give up. */
    if (tmp && anyexists && f_open == NULL)
1734
	goto cleanup_and_exit;
1735

1736
1737
1738
    /* 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)) {
1739
1740
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1741
1742
1743
	goto cleanup_and_exit;
    }

1744
#ifndef NANO_TINY
1745
    /* Check whether the file (at the end of the symlink) exists. */
1746
    realexists = (stat(realname, &st) != -1);
1747

1748
1749
1750
1751
    /* If we haven't stat()d this file before (say, the user just
     * specified it interactively), stat and save the value now,
     * or else we will chase null pointers when we do modtime checks,
     * preserve file times, and so on, during backup. */
1752
1753
    if (openfile->current_stat == NULL && !tmp && realexists) {
	openfile->current_stat = (struct stat *)nmalloc(sizeof(struct stat));
1754
	stat(realname, openfile->current_stat);
1755
    }
1756

1757
    /* We backup only if the backup toggle is set, the file isn't
1758
1759
1760
1761
     * 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. */
1762
1763
1764
    if (ISSET(BACKUP_FILE) && !tmp && realexists && ((append != OVERWRITE ||
		openfile->mark_set) || (openfile->current_stat &&
		openfile->current_stat->st_mtime == st.st_mtime))) {
1765
	int backup_fd;
1766
	FILE *backup_file;
1767
	char *backupname;
1768
	struct utimbuf filetime;
1769
	int copy_status;
1770
	int backup_cflags;
1771

1772
	/* Save the original file's access and modification times. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1773
1774
	filetime.actime = openfile->current_stat->st_atime;
	filetime.modtime = openfile->current_stat->st_mtime;
1775

1776
1777
1778
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1779

1780
1781
1782
	    if (f == NULL) {
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1783
		beep();
1784
1785
1786
1787
		/* 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;
1788
	    }
1789
1790
	}

1791
	/* If backup_dir is set, we set backupname to
1792
1793
1794
1795
	 * 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]. */
1796
	if (backup_dir != NULL) {
1797
	    char *backuptemp = get_full_path(realname);
1798

1799
	    if (backuptemp == NULL)
1800
1801
1802
1803
1804
1805
		/* 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~. */
1806
		backuptemp = mallocstrcpy(NULL, tail(realname));
1807
	    else {
1808
1809
		size_t i = 0;

1810
1811
1812
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1813
1814
1815
1816
		}
	    }

	    backupname = charalloc(strlen(backup_dir) +
1817
1818
1819
1820
		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
1821
	    if (*backuptemp == '\0') {
1822
		statusbar(_("Error writing backup file %s: %s"), backupname,
1823
1824
1825
		    _("Too many backup files?"));
		free(backuptemp);
		free(backupname);
1826
1827
1828
1829
1830
		/* If we can't write to the backup, DON'T 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! */
1831
		goto cleanup_and_exit;
1832
1833
1834
1835
	    } else {
		free(backupname);
		backupname = backuptemp;
	    }
1836
1837
1838
1839
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1840

1841
	/* First, unlink any existing backups.  Next, open the backup
1842
1843
	 * file with O_CREAT and O_EXCL.  If it succeeds, we have a file
	 * descriptor to a new backup file. */
1844
	if (unlink(backupname) < 0 && errno != ENOENT && !ISSET(INSECURE_BACKUP)) {
1845
1846
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1847
1848
	    statusbar(_("Error writing backup file %s: %s"), backupname,
			strerror(errno));
1849
1850
1851
1852
	    free(backupname);
	    goto cleanup_and_exit;
	}

1853
1854
	if (ISSET(INSECURE_BACKUP))
	    backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
1855
	else
1856
1857
1858
	    backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

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

1864
1865
1866
1867
1868
1869
	if (backup_fd < 0 || backup_file == NULL) {
	    statusbar(_("Error writing backup file %s: %s"), backupname,
			strerror(errno));
	    free(backupname);
	    goto cleanup_and_exit;
	}
1870

1871
1872
	/* We shouldn't worry about chown()ing something if we're not
	 * root, since it's likely to fail! */
1873
	if (geteuid() == NANO_ROOT_UID && fchown(backup_fd,
1874
		openfile->current_stat->st_uid, openfile->current_stat->st_gid) == -1
1875
1876
1877
		&& !ISSET(INSECURE_BACKUP)) {
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1878
1879
1880
1881
1882
1883
1884
	    statusbar(_("Error writing backup file %s: %s"), backupname,
		strerror(errno));
	    free(backupname);
	    fclose(backup_file);
	    goto cleanup_and_exit;
	}

1885
1886
1887
1888
	if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1
		&& !ISSET(INSECURE_BACKUP)) {
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1889
	    statusbar(_("Error writing backup file %s: %s"), backupname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1890
		strerror(errno));
1891
	    free(backupname);
1892
	    fclose(backup_file);
1893
	    /* If we can't write to the backup, DONT go on, since
1894
	     * whatever caused the backup file to fail (e.g. disk
1895
	     * full) may well cause the real file write to fail, which
1896
	     * means we could lose both the backup and the original! */
1897
	    goto cleanup_and_exit;
1898
1899
1900
	}

#ifdef DEBUG
1901
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1902
1903
#endif

1904
1905
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1906

1907
1908
	if (copy_status != 0) {
	    statusbar(_("Error reading %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1909
			strerror(errno));
1910
1911
1912
1913
1914
1915
	    beep();
	    goto cleanup_and_exit;
	}

	/* And set its metadata. */
	if (utime(backupname, &filetime) == -1 && !ISSET(INSECURE_BACKUP)) {
1916
1917
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1918
	    statusbar(_("Error writing backup file %s: %s"), backupname,
1919
			strerror(errno));
1920
1921
1922
1923
	    /* If we can't write to the backup, DON'T 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! */
1924
	    goto cleanup_and_exit;
1925
	}
1926

1927
1928
	free(backupname);
    }
1929

1930
    skip_backup:
1931
#endif /* !NANO_TINY */
1932

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1933
1934
    /* 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
1935
1936
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
1937
		unlink(realname) == -1) {
1938
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1939
	goto cleanup_and_exit;
1940
    }
1941

1942
1943
    if (f_open == NULL) {
	original_umask = umask(0);
1944

1945
	/* If we create a temp file, we don't let anyone else access it.
1946
1947
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1948
	    umask(S_IRWXG | S_IRWXO);
1949
1950
	else
	    umask(original_umask);
1951
    }
1952

1953
    /* If we're prepending, copy the file to a temp file. */
1954
    if (append == PREPEND) {
1955
1956
1957
	int fd_source;
	FILE *f_source = NULL;

1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
	if (f == NULL) {
	    f = fopen(realname, "rb");

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

1969
	tempname = safe_tempfile(&f);
1970

1971
	if (tempname == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1972
	    statusbar(_("Error writing temp file: %s"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1973
		strerror(errno));
1974
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1975
	}
1976

1977
	if (f_open == NULL) {
1978
	    fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1979

1980
1981
1982
1983
1984
	    if (fd_source != -1) {
		f_source = fdopen(fd_source, "rb");
		if (f_source == NULL) {
		    statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1985
		    beep();
1986
1987
1988
1989
1990
1991
		    close(fd_source);
		    fclose(f);
		    unlink(tempname);
		    goto cleanup_and_exit;
		}
	    }
1992
1993
	}

1994
	if (f_source == NULL || copy_file(f_source, f) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1995
1996
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1997
	    unlink(tempname);
1998
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1999
2000
2001
	}
    }

2002
2003
2004
    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
2005
2006
2007
	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);
2008

2009
2010
	/* Set the umask back to the user's original value. */
	umask(original_umask);
2011

2012
2013
2014
2015
	/* If we couldn't open the file, give up. */
	if (fd == -1) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
2016

2017
2018
2019
2020
2021
	    /* tempname has been set only if we're prepending. */
	    if (tempname != NULL)
		unlink(tempname);
	    goto cleanup_and_exit;
	}
2022

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

2025
2026
2027
2028
2029
2030
	if (f == NULL) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
	    close(fd);
	    goto cleanup_and_exit;
	}
2031
2032
    }

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

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

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

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

2045
2046
	/* Convert nulls to newlines.  data_len is the string's real
	 * length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2047
2048
	unsunder(fileptr->data, data_len);

2049
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2050
2051
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
2052
	    fclose(f);
2053
	    goto cleanup_and_exit;
2054
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2055

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2056
	/* If we're on the last line of the file, don't write a newline
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2057
2058
2059
2060
2061
2062
2063
	 * 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 {
2064
#ifndef NANO_TINY
2065
	    if (openfile->fmt == DOS_FILE || openfile->fmt == MAC_FILE) {
2066
2067
		if (putc('\r', f) == EOF) {
		    statusbar(_("Error writing %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2068
			strerror(errno));
2069
2070
2071
		    fclose(f);
		    goto cleanup_and_exit;
		}
2072
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2073

2074
	    if (openfile->fmt != MAC_FILE)
2075
#endif
2076
2077
		if (putc('\n', f) == EOF) {
		    statusbar(_("Error writing %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2078
			strerror(errno));
2079
2080
2081
2082
		    fclose(f);
		    goto cleanup_and_exit;
		}
	}
Chris Allegretta's avatar
Chris Allegretta committed
2083
2084
2085
2086
2087

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

2088
    /* If we're prepending, open the temp file, and append it to f. */
2089
    if (append == PREPEND) {
2090
2091
2092
	int fd_source;
	FILE *f_source = NULL;

2093
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
2094

2095
2096
2097
2098
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
2099
	}
2100

2101
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2102
2103
	    statusbar(_("Error reading %s: %s"), tempname,
		strerror(errno));
2104
	    beep();
2105
	    fclose(f);
2106
	    goto cleanup_and_exit;
2107
2108
	}

2109
	if (copy_file(f_source, f) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2110
2111
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
2112
	    goto cleanup_and_exit;
2113
	}
2114
2115

	unlink(tempname);
2116
2117
2118
2119
2120
    } else if (fclose(f) != 0) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
	    goto cleanup_and_exit;
    }
2121

2122
    if (!tmp && append == OVERWRITE) {
Chris Allegretta's avatar
Chris Allegretta committed
2123
	if (!nonamechange) {
2124
2125
	    openfile->filename = mallocstrcpy(openfile->filename,
		realname);
2126
#ifndef DISABLE_COLOR
2127
	    /* We might have changed the filename, so update the colors
2128
2129
	     * to account for it, and then make sure we're using
	     * them. */
2130
	    color_update();
2131
	    color_init();
2132
2133
2134

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

2140
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2141
2142
2143
2144
	/* 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));
2145
2146
	if (!openfile->mark_set)
	    stat(realname, openfile->current_stat);
2147
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2148

2149
2150
	statusbar(P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten),
2151
		(unsigned long)lineswritten);
2152
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2153
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2154
    }
2155

2156
    retval = TRUE;
2157
2158
2159

  cleanup_and_exit:
    free(realname);
2160
    free(tempname);
2161

2162
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2163
2164
}

2165
#ifndef NANO_TINY
2166
2167
/* Write a marked selection from a file out to disk.  Return TRUE on
 * success or FALSE on error. */
2168
bool write_marked_file(const char *name, FILE *f_open, bool tmp,
2169
	append_type append)
2170
{
2171
    bool retval;
2172
2173
    bool old_modified = openfile->modified;
	/* write_file() unsets the modified flag. */
2174
    bool added_magicline = FALSE;
2175
2176
2177
2178
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

2179
2180
    assert(openfile->mark_set);

2181
2182
2183
    /* Partition the filestruct so that it contains only the marked
     * text. */
    mark_order((const filestruct **)&top, &top_x,
2184
	(const filestruct **)&bot, &bot_x, NULL);
2185
    filepart = partition_filestruct(top, top_x, bot, bot_x);
2186

2187
2188
2189
2190
2191
    /* 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) &&
2192
		(added_magicline = (openfile->filebot->data[0] != '\0')))
2193
	new_magicline();
2194

2195
    retval = write_file(name, f_open, tmp, append, TRUE);
2196

2197
2198
2199
    /* If the NO_NEWLINES flag isn't set, and we added a magicline,
     * remove it now. */
    if (!ISSET(NO_NEWLINES) && added_magicline)
2200
2201
2202
2203
	remove_magicline();

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

2206
    if (old_modified)
2207
2208
2209
2210
	set_modified();

    return retval;
}
2211

2212
#endif /* !NANO_TINY */
2213

2214
/* Write the current file to disk.  If the mark is on, write the current
2215
2216
2217
2218
2219
 * marked selection to disk.  If exiting is TRUE, write the entire file
 * to disk regardless of whether the mark is on, and without prompting if
 * the TEMP_FILE flag is set and the current file has a name.  Return 0
 * on error, 1 on success, and 2 when the buffer is to be discarded. */
int do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
2220
{
2221
    int i;
2222
    append_type append = OVERWRITE;
2223
    char *ans;
2224
	/* The last answer the user typed at the statusbar prompt. */
2225
#ifndef DISABLE_EXTRA
2226
    static bool did_credits = FALSE;
2227
#endif
2228
    bool result = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2229

2230
    if (exiting && openfile->filename[0] != '\0' && ISSET(TEMP_FILE)) {
2231
	result = write_file(openfile->filename, NULL, FALSE, OVERWRITE, FALSE);
2232

2233
2234
	if (result)
	    return 1;	/* The write succeeded. */
Chris Allegretta's avatar
Chris Allegretta committed
2235
2236
    }

2237
    ans = mallocstrcpy(NULL,
2238
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2239
	(!exiting && openfile->mark_set) ? "" :
2240
#endif
2241
	openfile->filename);
2242
2243
2244

    while (TRUE) {
	const char *msg;
2245
#ifndef NANO_TINY
2246
2247
	const char *formatstr, *backupstr;

2248
2249
	formatstr = (openfile->fmt == DOS_FILE) ? _(" [DOS Format]") :
		    (openfile->fmt == MAC_FILE) ? _(" [Mac Format]") : "";
2250

2251
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2252

2253
	/* If we're using restricted mode, don't display the "Write
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2254
2255
2256
	 * Selection to File" prompt.  This function is disabled, since
	 * it allows reading from or writing to files not specified on
	 * the command line. */
2257
	if (!ISSET(RESTRICTED) && !exiting && openfile->mark_set)
2258
2259
2260
	    msg = (append == PREPEND) ? _("Prepend Selection to File") :
		  (append == APPEND) ? _("Append Selection to File") :
		  _("Write Selection to File");
2261
	else
2262
#endif /* !NANO_TINY */
2263
	    msg = (append == PREPEND) ? _("File Name to Prepend to") :
2264
2265
		  (append == APPEND) ? _("File Name to Append to") :
		  _("File Name to Write");
2266

2267
2268
	/* If we're using restricted mode, and the filename isn't blank,
	 * disable tab completion. */
2269
	i = do_prompt(!ISSET(RESTRICTED) ||
2270
2271
2272
2273
		openfile->filename[0] == '\0',
#ifndef DISABLE_TABCOMP
		TRUE,
#endif
2274
		MWRITEFILE, ans,
2275
#ifndef DISABLE_HISTORIES
2276
2277
		NULL,
#endif
2278
		edit_refresh, "%s%s%s", msg,
2279
2280
#ifndef NANO_TINY
		formatstr, backupstr
2281
#else
2282
		"", ""
2283
2284
2285
#endif
		);

2286
2287
	/* 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
2288
	if (i < 0 || *answer == '\n') {
2289
	    statusbar(_("Cancelled"));
2290
2291
	    break;
	} else {
2292
2293
	    functionptrtype func = func_from_key(&i);

2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
	    /* Upon request, abandon the buffer, if user is sure. */
	    if (func == discard_buffer) {
		if (openfile->modified)
		    i = do_yesno_prompt(FALSE,
				_("Save modified buffer anyway ? "));
		else
		    i = 0;

		if (i == 0) {
		    free(ans);
		    return 2;	/* Yes, discard the buffer. */
2305
2306
		} else
		    continue;	/* Go back to the filename prompt. */
2307
2308
	    }

2309
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
2310

2311
#ifndef DISABLE_BROWSER
2312
	    if (func == to_files_void) {
2313
		char *tmp = do_browse_from(answer);
2314

2315
2316
		if (tmp == NULL)
		    continue;
2317

2318
		/* We have a file now.  Indicate this. */
2319
2320
2321
		free(answer);
		answer = tmp;
	    } else
2322
#endif /* !DISABLE_BROWSER */
2323
#ifndef NANO_TINY
2324
	    if (func == dos_format_void) {
2325
2326
		openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE :
			DOS_FILE;
2327
		continue;
2328
	    } else if (func == mac_format_void) {
2329
2330
		openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE :
			MAC_FILE;
2331
		continue;
2332
	    } else if (func == backup_file_void) {
2333
2334
2335
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
2336
#endif /* !NANO_TINY */
2337
	    if (func == prepend_void) {
2338
		append = (append == PREPEND) ? OVERWRITE : PREPEND;
2339
		continue;
2340
	    } else if (func == append_void) {
2341
		append = (append == APPEND) ? OVERWRITE : APPEND;
2342
		continue;
2343
	    } else if (func == do_help_void) {
2344
		continue;
2345
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2346

Chris Allegretta's avatar
Chris Allegretta committed
2347
#ifdef DEBUG
2348
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
2349
#endif
2350

2351
#ifndef DISABLE_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2352
2353
2354
2355
2356
2357
2358
	    /* 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) &&
2359
			strcasecmp(answer, "zzy") == 0) {
2360
		do_credits();
2361
		did_credits = TRUE;
2362
2363
		break;
	    }
2364
#endif
2365

2366
	    if (append == OVERWRITE) {
2367
		size_t answer_len = strlen(answer);
2368
		bool name_exists, do_warning;
2369
		char *full_answer, *full_filename;
2370
2371
		struct stat st;

2372
2373
2374
		/* Convert newlines to nulls, just before we get the
		 * full path. */
		sunder(answer);
2375

2376
2377
		full_answer = get_full_path(answer);
		full_filename = get_full_path(openfile->filename);
2378
2379
		name_exists = (stat((full_answer == NULL) ? answer :
			full_answer, &st) != -1);
2380
2381
2382
2383
2384
2385
		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);
2386

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2387
2388
2389
2390
		/* Convert nulls to newlines.  answer_len is the
		 * string's real length. */
		unsunder(answer, answer_len);

2391
2392
		free(full_filename);
		free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2393

2394
		if (do_warning) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2395
2396
2397
2398
2399
2400
2401
2402
		    /* 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;
2403

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2404
		    if (name_exists) {
2405
2406
2407
2408
			i = do_yesno_prompt(FALSE,
				_("File exists, OVERWRITE ? "));
			if (i == 0 || i == -1)
			    continue;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2409
		    } else
2410
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2411
		    if (exiting || !openfile->mark_set)
2412
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2413
		    {
2414
2415
2416
2417
2418
			i = do_yesno_prompt(FALSE,
				_("Save file under DIFFERENT NAME ? "));
			if (i == 0 || i == -1)
			    continue;
		    }
2419
		}
2420
#ifndef NANO_TINY
2421
2422
2423
		/* Complain if the file exists, the name hasn't changed,
		 * and the stat information we had before does not match
		 * what we have now. */
2424
2425
2426
2427
		else if (name_exists && openfile->current_stat &&
			(openfile->current_stat->st_mtime < st.st_mtime ||
			openfile->current_stat->st_dev != st.st_dev ||
			openfile->current_stat->st_ino != st.st_ino)) {
2428
2429
2430
2431
2432
		    i = do_yesno_prompt(FALSE,
			_("File was modified since you opened it, continue saving ? "));
		    if (i == 0 || i == -1)
			continue;
		}
2433
#endif
2434

2435
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2436

2437
	    /* Convert newlines to nulls, just before we save the file. */
2438
2439
2440
	    sunder(answer);
	    align(&answer);

2441
	    /* Here's where we allow the selected text to be written to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2442
2443
2444
	     * 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. */
2445
	    result =
2446
2447
2448
#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
2449
#endif
2450
		write_file(answer, NULL, FALSE, append, FALSE);
2451

2452
2453
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2454
    }
2455
2456

    free(ans);
2457

2458
    return result ? 1 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
2459
2460
}

2461
/* Write the current buffer to disk, or discard it. */
2462
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
2463
{
2464
2465
2466
2467
    /* If the user chose to discard the buffer, close it. */
    if (do_writeout(FALSE) == 2)
	close_and_go();

2468
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
2469
}
Chris Allegretta's avatar
Chris Allegretta committed
2470

2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
#ifndef NANO_TINY
/* If it has a name, write the current file to disk without prompting. */
void do_savefile(void)
{
    if (openfile->filename[0] != '\0')
	write_file(openfile->filename, NULL, FALSE, OVERWRITE, FALSE);
    else
	do_writeout_void();
}
#endif

2482
2483
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
2484
char *real_dir_from_tilde(const char *buf)
2485
{
2486
    char *retval;
2487

2488
    assert(buf != NULL);
2489

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2490
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2491
	size_t i = 1;
2492
	char *tilde_dir;
2493

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

2498
2499
2500
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
2501
	    tilde_dir = mallocstrcpy(NULL, homedir);
2502
2503
2504
	} else {
	    const struct passwd *userdata;

2505
2506
2507
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

2508
2509
	    do {
		userdata = getpwent();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2510
2511
	    } while (userdata != NULL && strcmp(userdata->pw_name,
		tilde_dir + 1) != 0);
2512
	    endpwent();
2513
	    if (userdata != NULL)
2514
		tilde_dir = mallocstrcpy(tilde_dir, userdata->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2515
	}
2516

2517
2518
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2519

2520
2521
2522
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2523

2524
    return retval;
2525
2526
}

2527
#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
2528
/* Our sort routine for file listings.  Sort alphabetically and
2529
 * case-insensitively, and sort directories before filenames. */
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
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;

2543
2544
2545
    /* 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
Benno Schulenberg's avatar
Benno Schulenberg committed
2546
     * have to use multibyte strcasecmp() instead. */
2547
    return mbstrcasecmp(a, b);
2548
}
2549
2550
2551
2552
2553

/* Free the memory allocated for array, which should contain len
 * elements. */
void free_chararray(char **array, size_t len)
{
2554
2555
    if (array == NULL)
	return;
2556

2557
2558
2559
2560
    for (; len > 0; len--)
	free(array[len - 1]);
    free(array);
}
2561
2562
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2563
#ifndef DISABLE_TABCOMP
2564
/* Is the given path a directory? */
2565
bool is_dir(const char *buf)
2566
{
2567
    char *dirptr;
2568
    struct stat fileinfo;
2569
2570
2571
    bool retval;

    assert(buf != NULL);
2572

2573
2574
    dirptr = real_dir_from_tilde(buf);

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

2577
    free(dirptr);
2578

2579
    return retval;
2580
}
Chris Allegretta's avatar
Chris Allegretta committed
2581

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2582
2583
2584
/* 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
2585
 * file, with the copyright years updated:
Chris Allegretta's avatar
Chris Allegretta committed
2586
2587
2588
 *
 * Termios command line History and Editting, originally
 * intended for NetBSD sh (ash)
2589
 * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
Chris Allegretta's avatar
Chris Allegretta committed
2590
2591
2592
2593
2594
2595
2596
2597
 *      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.
2598
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
2599

2600
/* We consider the first buf_len characters of buf for ~username tab
2601
2602
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
2603
	size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2604
{
2605
2606
    char **matches = NULL;
    const struct passwd *userdata;
2607

2608
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2609

2610
    *num_matches = 0;
2611

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

2617
2618
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2619
2620
2621
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2622
2623
#endif

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2624
2625
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
2626
2627
2628
	    matches[*num_matches] =
		charalloc(strlen(userdata->pw_name) + 2);
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2629
	    ++(*num_matches);
2630
	}
2631
2632
    }
    endpwent();
2633

2634
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2635
2636
}

2637
/* We consider the first buf_len characters of buf for filename tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2638
 * completion. */
2639
char **cwd_tab_completion(const char *buf, bool allow_files, size_t
2640
	*num_matches, size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2641
{
2642
    char *dirname = mallocstrcpy(NULL, buf), *filename;
2643
2644
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2645
    DIR *dir;
2646
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2647

2648
    assert(dirname != NULL && num_matches != NULL);
2649

2650
    *num_matches = 0;
2651
    null_at(&dirname, buf_len);
2652
2653
2654
2655
2656
2657
2658
2659

    /* 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
2660
	*tmpdirname = '\0';
2661
2662
2663
	tmpdirname = dirname;
	dirname = real_dir_from_tilde(dirname);
	free(tmpdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2664
    } else {
2665
2666
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2667
2668
    }

2669
    assert(dirname[strlen(dirname) - 1] == '/');
2670

Chris Allegretta's avatar
Chris Allegretta committed
2671
    dir = opendir(dirname);
2672

2673
    if (dir == NULL) {
2674
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2675
	beep();
2676
2677
2678
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2679
    }
2680
2681
2682

    filenamelen = strlen(filename);

2683
    while ((nextdir = readdir(dir)) != NULL) {
2684
2685
	bool skip_match = FALSE;

Chris Allegretta's avatar
Chris Allegretta committed
2686
#ifdef DEBUG
2687
	fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2688
#endif
2689
	/* See if this matches. */
2690
	if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
2691
2692
		(*filename == '.' || (strcmp(nextdir->d_name, ".") != 0 &&
		strcmp(nextdir->d_name, "..") != 0))) {
2693
2694
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2695

2696
2697
2698
2699
	    char *tmp = charalloc(strlen(dirname) +
		strlen(nextdir->d_name) + 1);
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

2700
2701
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2702
2703
2704
2705
2706
2707
2708
	     * 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. */
2709
	    if (!allow_files && !is_dir(tmp))
2710
2711
2712
		skip_match = TRUE;

	    free(tmp);
2713

2714
	    if (skip_match)
2715
		continue;
2716

2717
2718
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
2719
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2720
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2721
2722
	}
    }
2723

2724
2725
    closedir(dir);
    free(dirname);
2726
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2727

2728
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2729
2730
}

2731
/* Do tab completion.  place refers to how much the statusbar cursor
2732
2733
 * position should be advanced.  refresh_func is the function we will
 * call to refresh the edit window. */
2734
char *input_tab(char *buf, bool allow_files, size_t *place, bool
2735
	*lastwastab, void (*refresh_func)(void), bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2736
{
2737
    size_t num_matches = 0, buf_len;
2738
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2739

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2743
    *list = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2744

2745
2746
    /* 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
2747
    if (*place > 0 && *buf == '~') {
2748
	const char *bob = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2749

2750
2751
2752
2753
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2754

2755
2756
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
2757
	matches = cwd_tab_completion(buf, allow_files, &num_matches, *place);
2758

2759
2760
2761
    buf_len = strlen(buf);

    if (num_matches == 0 || *place != buf_len)
2762
2763
2764
2765
	beep();
    else {
	size_t match, common_len = 0;
	char *mzero;
2766
2767
2768
	const char *lastslash = revstrstr(buf, "/", buf + *place);
	size_t lastslash_len = (lastslash == NULL) ? 0 :
		lastslash - buf + 1;
2769
2770
2771
	char *match1_mb = charalloc(mb_cur_max() + 1);
	char *match2_mb = charalloc(mb_cur_max() + 1);
	int match1_mb_len, match2_mb_len;
2772
2773
2774

	while (TRUE) {
	    for (match = 1; match < num_matches; match++) {
2775
2776
		/* Get the number of single-byte characters that all the
		 * matches have in common. */
2777
		match1_mb_len = parse_mbchar(matches[0] + common_len,
2778
			match1_mb, NULL);
2779
		match2_mb_len = parse_mbchar(matches[match] +
2780
			common_len, match2_mb, NULL);
2781
2782
2783
		match1_mb[match1_mb_len] = '\0';
		match2_mb[match2_mb_len] = '\0';
		if (strcmp(match1_mb, match2_mb) != 0)
2784
2785
		    break;
	    }
2786

2787
	    if (match < num_matches || matches[0][common_len] == '\0')
2788
		break;
2789

2790
	    common_len += parse_mbchar(buf + common_len, NULL, NULL);
2791
	}
2792

2793
2794
2795
	free(match1_mb);
	free(match2_mb);

2796
	mzero = charalloc(lastslash_len + common_len + 1);
2797
2798
2799

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

2801
	common_len += lastslash_len;
2802
	mzero[common_len] = '\0';
2803

2804
	assert(common_len >= *place);
2805

2806
	if (num_matches == 1 && is_dir(mzero)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2807
	    mzero[common_len++] = '/';
2808

2809
2810
	    assert(common_len > *place);
	}
2811

2812
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2813
	    beep();
2814

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2815
	/* If there is more of a match to display on the statusbar, show
2816
	 * it.  We reset lastwastab to FALSE: it requires pressing Tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2817
	 * twice in succession with no statusbar changes to see a match
2818
2819
2820
	 * list. */
	if (common_len != *place) {
	    *lastwastab = FALSE;
2821
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2822
2823
	    charmove(buf + common_len, buf + *place, buf_len -
		*place + 1);
2824
	    strncpy(buf, mzero, common_len);
2825
	    *place = common_len;
2826
	} else if (!*lastwastab || num_matches < 2)
2827
2828
	    *lastwastab = TRUE;
	else {
2829
	    int longest_name = 0, ncols, editline = 0;
2830

2831
2832
2833
2834
2835
2836
2837
2838
	    /* 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
2839

2840
2841
		if (common_len > COLS - 1) {
		    longest_name = COLS - 1;
2842
2843
		    break;
		}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2844

2845
2846
		if (common_len > longest_name)
		    longest_name = common_len;
2847
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2848

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

2851
	    /* Each column will be (longest_name + 2) columns wide, i.e.
2852
2853
	     * two spaces between columns, except that there will be
	     * only one space after the last column. */
2854
	    ncols = (COLS + 1) / (longest_name + 2);
2855

2856
2857
2858
2859
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2860

2861
2862
	    /* Disable el cursor. */
	    curs_set(0);
2863

2864
2865
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2866

2867
		wmove(edit, editline, (longest_name + 2) *
2868
			(match % ncols));
2869

2870
		if (match % ncols == 0 &&
2871
			editline == editwinrows - 1 &&
2872
			num_matches - match > ncols) {
2873
2874
2875
		    waddstr(edit, _("(more)"));
		    break;
		}
2876

2877
2878
2879
2880
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2881

2882
		if ((match + 1) % ncols == 0)
2883
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2884
	    }
2885

2886
	    wnoutrefresh(edit);
2887
	    *list = TRUE;
2888
2889
2890
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2891
2892
    }

2893
    free_chararray(matches, num_matches);
2894

2895
    /* Only refresh the edit window if we don't have a list of filename
2896
     * matches on it. */
2897
    if (!*list)
2898
	refresh_func();
2899
2900

    /* Enable el cursor. */
2901
    curs_set(1);
2902

2903
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2904
}
2905
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2906

2907
2908
/* Only print the last part of a path.  Isn't there a shell command for
 * this? */
2909
2910
const char *tail(const char *foo)
{
2911
    const char *tmp = strrchr(foo, '/');
2912

2913
2914
    if (tmp == NULL)
	tmp = foo;
2915
    else
2916
2917
2918
2919
2920
	tmp++;

    return tmp;
}

2921
2922
#ifndef DISABLE_HISTORIES
/* Return the constructed dirfile path, or NULL if we can't find the home
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2923
2924
 * directory.  The string is dynamically allocated, and should be
 * freed. */
2925
char *construct_filename(const char *str)
2926
{
2927
    char *newstr = NULL;
2928

2929
2930
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2931

2932
2933
2934
	newstr = charalloc(homelen + strlen(str) + 1);
	strcpy(newstr, homedir);
	strcpy(newstr + homelen, str);
Chris Allegretta's avatar
Chris Allegretta committed
2935
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2936

2937
2938
2939
2940
2941
    return newstr;
}

char *histfilename(void)
{
2942
    return construct_filename("/.nano/search_history");
2943
2944
}

2945
2946
/* Construct the legacy history filename.
 * (Deprecate in 2.5, delete later.) */
2947
2948
2949
2950
2951
2952
2953
char *legacyhistfilename(void)
{
    return construct_filename("/.nano_history");
}

char *poshistfilename(void)
{
2954
    return construct_filename("/.nano/filepos_history");
2955
2956
2957
2958
}

void history_error(const char *msg, ...)
{
2959
    va_list ap;
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969

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

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

2970
2971
2972
/* 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. */
2973
2974
int check_dotnano(void)
{
2975
    int ret = 1;
2976
2977
2978
2979
2980
    struct stat dirstat;
    char *nanodir = construct_filename("/.nano");

    if (stat(nanodir, &dirstat) == -1) {
	if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
2981
2982
2983
	    history_error(N_("Unable to create directory %s: %s\n"
			     "It is required for saving/loading search history or cursor positions.\n"),
				nanodir, strerror(errno));
2984
	    ret = 0;
2985
2986
	}
    } else if (!S_ISDIR(dirstat.st_mode)) {
2987
2988
2989
	history_error(N_("Path %s is not a directory and needs to be.\n"
			 "Nano will be unable to load or save search history or cursor positions.\n"),
				nanodir);
2990
	ret = 0;
2991
    }
2992
2993
2994

    free(nanodir);
    return ret;
2995
2996
}

2997
/* Load the search and replace histories from ~/.nano/search_history. */
2998
2999
3000
void load_history(void)
{
    char *nanohist = histfilename();
3001
3002
3003
    char *legacyhist = legacyhistfilename();
    struct stat hstat;

3004
    if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
3005
	if (rename(legacyhist, nanohist) == -1)
3006
3007
3008
	    history_error(N_("Detected a legacy nano history file (%s) which I tried to move\n"
			     "to the preferred location (%s) but encountered an error: %s"),
				legacyhist, nanohist, strerror(errno));
3009
	else
3010
3011
3012
	    history_error(N_("Detected a legacy nano history file (%s) which I moved\n"
			     "to the preferred location (%s)\n(see the nano FAQ about this change)"),
				legacyhist, nanohist);
3013
3014
    }

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

3019
	if (hist == NULL) {
3020
	    if (errno != ENOENT) {
3021
3022
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
3023
		history_error(N_("Error reading %s: %s"), nanohist,
3024
			strerror(errno));
3025
	    }
3026
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3027
3028
3029
	    /* 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. */
3030
	    filestruct **history = &search_history;
3031
	    char *line = NULL;
3032
	    size_t buf_len = 0;
3033
3034
	    ssize_t read;

3035
	    while ((read = getline(&line, &buf_len, hist)) >= 0) {
3036
3037
3038
3039
3040
3041
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
3042
3043
		    update_history(history, line);
		} else
3044
3045
		    history = &replace_history;
	    }
3046

3047
	    fclose(hist);
3048
	    free(line);
3049
	}
3050
	free(nanohist);
3051
	free(legacyhist);
3052
3053
3054
    }
}

3055
3056
3057
/* 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. */
3058
bool writehist(FILE *hist, filestruct *h)
3059
{
3060
    filestruct *p;
3061

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

3067
	sunder(p->data);
3068

3069
	if (fwrite(p->data, sizeof(char), p_len, hist) < p_len ||
3070
3071
		putc('\n', hist) == EOF)
	    return FALSE;
3072
    }
3073

3074
    return TRUE;
3075
3076
}

3077
/* Save the search and replace histories to ~/.nano/search_history. */
3078
3079
void save_history(void)
{
3080
    char *nanohist;
3081

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3082
    /* Don't save unchanged or empty histories. */
3083
    if (!history_has_changed() || (searchbot->lineno == 1 &&
3084
		replacebot->lineno == 1))
3085
3086
	return;

3087
3088
3089
3090
    nanohist = histfilename();

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

3092
	if (hist == NULL)
3093
3094
	    fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
			strerror(errno));
3095
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3096
3097
	    /* Make sure no one else can read from or write to the
	     * history file. */
3098
	    chmod(nanohist, S_IRUSR | S_IWUSR);
3099

3100
	    if (!writehist(hist, searchage) || !writehist(hist, replaceage))
3101
		fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
3102
			strerror(errno));
3103

3104
3105
	    fclose(hist);
	}
3106

3107
3108
3109
	free(nanohist);
    }
}
3110

3111
/* Save the recorded last file positions to ~/.nano/filepos_history. */
3112
3113
void save_poshistory(void)
{
3114
    char *poshist = poshistfilename();
3115
3116
    char *statusstr = NULL;
    poshiststruct *posptr;
3117
    FILE *hist;
3118

3119
3120
    if (poshist == NULL)
	return;
3121

3122
    hist = fopen(poshist, "wb");
3123

3124
3125
3126
3127
3128
    if (hist == NULL)
	fprintf(stderr, _("Error writing %s: %s\n"), poshist, strerror(errno));
    else {
	/* Don't allow others to read or write the history file. */
	chmod(poshist, S_IRUSR | S_IWUSR);
3129

3130
3131
3132
	for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
	    statusstr = charalloc(strlen(posptr->filename) + 2 * sizeof(ssize_t) + 4);
	    sprintf(statusstr, "%s %ld %ld\n", posptr->filename, (long)posptr->lineno,
3133
			(long)posptr->xno);
3134
3135
3136
	    if (fwrite(statusstr, sizeof(char), strlen(statusstr), hist) < strlen(statusstr))
		fprintf(stderr, _("Error writing %s: %s\n"), poshist, strerror(errno));
	    free(statusstr);
3137
	}
3138
	fclose(hist);
3139
    }
3140
    free(poshist);
3141
3142
}

3143
3144
/* Update the recorded last file positions, given a filename, a line
 * and a column.  If no entry is found, add a new one at the end. */
3145
3146
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
3147
3148
    poshiststruct *posptr, *posprev = NULL;
    char *fullpath = get_full_path(filename);
3149
3150

    if (fullpath == NULL)
3151
	return;
3152

3153
    for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
3154
	if (!strcmp(posptr->filename, fullpath)) {
3155
3156
	    posptr->lineno = lineno;
	    posptr->xno = xpos;
3157
	    free(fullpath);
3158
3159
	    return;
	}
3160
3161
3162
3163
	posprev = posptr;
    }

    /* Didn't find it, make a new node yo! */
3164
    posptr = (poshiststruct *)nmalloc(sizeof(poshiststruct));
3165
3166
3167
3168
3169
    posptr->filename = mallocstrcpy(NULL, fullpath);
    posptr->lineno = lineno;
    posptr->xno = xpos;
    posptr->next = NULL;

3170
3171
    if (position_history == NULL)
	position_history = posptr;
3172
3173
3174
3175
3176
3177
    else
	posprev->next = posptr;

    free(fullpath);
}

3178
3179
3180
/* Check the recorded last file positions to see if the given file
 * matches an existing entry.  If so, return 1 and set line and column
 * to the retrieved values.  Otherwise, return 0. */
3181
3182
3183
3184
3185
3186
int check_poshistory(const char *file, ssize_t *line, ssize_t *column)
{
    poshiststruct *posptr;
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
3187
	return 0;
3188

3189
    for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
3190
3191
3192
	if (!strcmp(posptr->filename, fullpath)) {
	    *line = posptr->lineno;
	    *column = posptr->xno;
3193
	    free(fullpath);
3194
3195
3196
	    return 1;
	}
    }
3197
    free(fullpath);
3198
3199
3200
    return 0;
}

3201
/* Load the recorded file positions from ~/.nano/filepos_history. */
3202
3203
void load_poshistory(void)
{
3204
    char *poshist = poshistfilename();
3205
    FILE *hist;
3206

3207
3208
3209
    /* If the home directory is missing, do_rcfile() will have reported it. */
    if (poshist == NULL)
	return;
3210

3211
    hist = fopen(poshist, "rb");
3212

3213
3214
    if (hist == NULL) {
	if (errno != ENOENT) {
3215
	    /* When reading failed, don't save history when we quit. */
3216
3217
	    UNSET(POS_HISTORY);
	    history_error(N_("Error reading %s: %s"), poshist, strerror(errno));
3218
	}
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
    } else {
	char *line = NULL, *lineptr, *xptr;
	size_t buf_len = 0;
	ssize_t read;
	poshiststruct *record_ptr = NULL, *newrecord;

	/* Read and parse each line, and store the extracted data. */
	while ((read = getline(&line, &buf_len, hist)) > 2) {
	    if (line[read - 1] == '\n')
		line[--read] = '\0';
	    unsunder(line, read);

	    lineptr = parse_next_word(line);
	    xptr = parse_next_word(lineptr);

	    /* Create a new position record. */
	    newrecord = (poshiststruct *)nmalloc(sizeof(poshiststruct));
	    newrecord->filename = mallocstrcpy(NULL, line);
	    newrecord->lineno = atoi(lineptr);
	    newrecord->xno = atoi(xptr);
	    newrecord->next = NULL;

	    /* Add the record to the list. */
	    if (position_history == NULL)
		position_history = newrecord;
	    else
		record_ptr->next = newrecord;

	    record_ptr = newrecord;
	}
	fclose(hist);
	free(line);
3251
    }
3252
    free(poshist);
3253
}
3254
#endif /* !DISABLE_HISTORIES */