files.c 92 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
55
56
57
58
59
60
61
62
    /* Initialize the new buffer. */
    initialize_buffer();
}

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

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

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

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

71
    openfile->modified = FALSE;
72
#ifndef NANO_TINY
73
    openfile->mark_set = FALSE;
74

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

78
    openfile->fmt = NIX_FILE;
79

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
80
    openfile->current_stat = NULL;
81
82
    openfile->undotop = NULL;
    openfile->current_undo = NULL;
83
    openfile->lock_filename = NULL;
84
#endif
85
#ifndef DISABLE_COLOR
86
87
    openfile->colorstrings = NULL;
#endif
88
}
Chris Allegretta's avatar
Chris Allegretta committed
89

90
91
92
/* Initialize the text of the current entry of the openfile
 * openfilestruct. */
void initialize_buffer_text(void)
93
{
94
    assert(openfile != NULL);
95

96
97
    openfile->fileage = make_new_node(NULL);
    openfile->fileage->data = mallocstrcpy(NULL, "");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
98

99
100
101
102
    openfile->filebot = openfile->fileage;
    openfile->edittop = openfile->fileage;
    openfile->current = openfile->fileage;

103
#ifndef DISABLE_COLOR
104
105
106
    openfile->fileage->multidata = NULL;
#endif

107
    openfile->totsize = 0;
108
109
}

110
#ifndef NANO_TINY
111
112
113
114
115
116
117
118
119
120
121
/* 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. */
122
123
124
125
126
127
128
129
130
131
132
133
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;
    char *lockdata = charalloc(1024);
    char myhostname[32];
    ssize_t lockdatalen = 1024;
    ssize_t wroteamt;

134
135
    /* Run things which might fail first before we try and blow away the
     * old state. */
136
137
138
139
140
141
142
143
    myuid = geteuid();
    if ((mypwuid = getpwuid(myuid)) == NULL) {
        statusbar(_("Couldn't determine my identity for lock file (getpwuid() failed)"));
        return -1;
    }
    mypid = getpid();

    if (gethostname(myhostname, 31) < 0) {
144
       statusbar(_("Couldn't determine hostname for lock file: %s"), strerror(errno));
145
146
147
148
149
150
151
152
153
154
155
156
157
       return -1;
    }

    if (delete_lockfile(lockfilename) < 0)
        return -1;

    if (ISSET(INSECURE_BACKUP))
        cflags = O_WRONLY | O_CREAT | O_APPEND;
    else
        cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

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

159
160
161
    /* Maybe we just don't have write access.  Don't stop us from
     * opening the file at all, just don't set the lock_filename and
     * return success. */
162
163
164
    if (fd < 0 && errno == EACCES)
        return 1;

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

    if (fd < 0 || filestream == NULL) {
        statusbar(_("Error writing lock file %s: %s"), lockfilename,
		    strerror(errno));
        return -1;
    }

175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
    /* 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. */
193
194
195
196
197
198
199
200
201
    null_at(&lockdata, lockdatalen);
    lockdata[0] = 0x62;
    lockdata[1] = 0x30;
    lockdata[24] = mypid % 256;
    lockdata[25] = mypid / 256;
    snprintf(&lockdata[2], 10, "nano %s", VERSION);
    strncpy(&lockdata[28], mypwuid->pw_name, 16);
    strncpy(&lockdata[68], myhostname, 31);
    strncpy(&lockdata[108], origfilename, 768);
202
203
    if (modified == TRUE)
        lockdata[1007] = 0x55;
204
205
206
207
208
209
210
211
212

    wroteamt = fwrite(lockdata, sizeof(char), lockdatalen, filestream);
    if (wroteamt < lockdatalen) {
        statusbar(_("Error writing lock file %s: %s"),
                  lockfilename, ferror(filestream));
        return -1;
    }

#ifdef DEBUG
213
    fprintf(stderr, "In write_lockfile(), write successful (wrote %lu bytes)\n", (unsigned long)wroteamt);
214
#endif
215
216
217
218
219
220
221
222
223
224
225
226

    if (fclose(filestream) == EOF) {
        statusbar(_("Error writing lock file %s: %s"),
                  lockfilename, strerror(errno));
        return -1;
    }

    openfile->lock_filename = lockfilename;

    return 1;
}

227
228
/* Less exciting, delete the lockfile.  Return -1 if unsuccessful and
 * complain on the statusbar, 1 otherwise. */
229
230
231
232
233
234
235
236
237
238
int delete_lockfile(const char *lockfilename)
{
    if (unlink(lockfilename) < 0 && errno != ENOENT) {
        statusbar(_("Error deleting lock file %s: %s"), lockfilename,
		    strerror(errno));
        return -1;
    }
    return 1;
}

239
240
241
242
/* 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. */
243
244
245
246
int do_lockfile(const char *filename)
{
    char *lockdir = dirname((char *) mallocstrcpy(NULL, filename));
    char *lockbase = basename((char *) mallocstrcpy(NULL, filename));
247
248
249
    size_t lockfilesize = strlen(filename) + strlen(locking_prefix)
		+ strlen(locking_suffix) + 3;
    char *lockfilename = charalloc(lockfilesize);
250
251
252
253
254
255
256
257
    char lockprog[12], lockuser[16];
    struct stat fileinfo;
    int lockfd, lockpid;

    snprintf(lockfilename, lockfilesize, "%s/%s%s%s", lockdir,
             locking_prefix, lockbase, locking_suffix);
#ifdef DEBUG
    fprintf(stderr, "lock file name is %s\n", lockfilename);
258
#endif
259
260
261
    if (stat(lockfilename, &fileinfo) != -1) {
        ssize_t readtot = 0;
        ssize_t readamt = 0;
262
263
        char *lockbuf = charalloc(8192);
        char *promptstr = charalloc(128);
264
265
        int ans;
        if ((lockfd = open(lockfilename, O_RDONLY)) < 0) {
266
            statusbar(_("Error opening lock file %s: %s"),
267
268
269
270
271
272
273
274
275
                      lockfilename, strerror(errno));
            return -1;
        }
        do {
            readamt = read(lockfd, &lockbuf[readtot], BUFSIZ);
            readtot += readamt;
        } while (readtot < 8192 && readamt > 0);

        if (readtot < 48) {
276
            statusbar(_("Error reading lock file %s: Not enough data read"),
277
278
279
280
281
282
283
284
285
286
287
288
                      lockfilename);
            return -1;
        }
        strncpy(lockprog, &lockbuf[2], 10);
        lockpid = lockbuf[25] * 256 + lockbuf[24];
        strncpy(lockuser, &lockbuf[28], 16);
#ifdef DEBUG
        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);
289
#endif
290
291
292
293
294
295
296
297
298
299
300
        sprintf(promptstr, "File being edited (by %s, PID %d, user %s), continue?",
                              lockprog, lockpid, lockuser);
        ans = do_yesno_prompt(FALSE, promptstr);
        if (ans < 1) {
            blank_statusbar();
            return -1;
        }
    }

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

303
304
/* 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. */
305
void open_buffer(const char *filename, bool undoable)
306
{
307
    bool new_buffer = (openfile == NULL
308
#ifndef DISABLE_MULTIBUFFER
309
	 || ISSET(MULTIBUFFER)
310
#endif
311
312
313
314
315
316
	);
	/* 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. */
317

318
319
    assert(filename != NULL);

320
321
322
323
324
325
326
#ifndef DISABLE_OPERATINGDIR
    if (check_operating_dir(filename, FALSE)) {
	statusbar(_("Can't insert file from outside of %s"),
		operating_dir);
	return;
    }
#endif
327

328
329
    /* If we're loading into a new buffer, add a new entry to
     * openfile. */
330
331
    if (new_buffer)
	make_new_buffer();
332

333
334
335
336
    /* 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)) ?
		open_file(filename, new_buffer, &f) : -2;
337

338
339
    /* If we have a file, and we're loading into a new buffer, update
     * the filename. */
340
341
    if (rc != -1 && new_buffer)
	openfile->filename = mallocstrcpy(openfile->filename, filename);
342

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
343
344
    /* If we have a non-new file, read it in.  Then, if the buffer has
     * no stat, update the stat, if applicable. */
345
    if (rc > 0) {
346
	read_file(f, rc, filename, undoable, new_buffer);
347
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
348
349
350
351
352
	if (openfile->current_stat == NULL) {
	    openfile->current_stat =
		(struct stat *)nmalloc(sizeof(struct stat));
	    stat(filename, openfile->current_stat);
	}
353
#endif
354
355
    }

356
    /* If we have a file, and we're loading into a new buffer, move back
357
358
     * to the beginning of the first line of the buffer. */
    if (rc != -1 && new_buffer) {
359
	openfile->current = openfile->fileage;
360
361
362
	openfile->current_x = 0;
	openfile->placewewant = 0;
    }
363

364
#ifndef DISABLE_COLOR
365
366
    /* If we're loading into a new buffer, update the colors to account
     * for it, if applicable. */
367
368
369
    if (new_buffer)
	color_update();
#endif
370
}
371

372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
#ifndef DISABLE_SPELLER
/* If it's not "", filename is a file to open.  We blow away the text of
 * the current buffer, and then open and read the file, if
 * applicable.  Note that we skip the operating directory test when
 * doing this. */
void replace_buffer(const char *filename)
{
    FILE *f;
    int rc;
	/* rc == -2 means that we have a new file.  -1 means that the
	 * open() failed.  0 means that the open() succeeded. */

    assert(filename != NULL);

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

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

    /* If we have a non-new file, read it in. */
395
    if (rc > 0)
396
	read_file(f, rc, filename, FALSE, TRUE);
397
398
399
400
401
402
403
404

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

405
/* Update the screen to account for the current buffer. */
406
void display_buffer(void)
407
{
408
    /* Update the titlebar, since the filename may have changed. */
409
    titlebar(NULL);
410

411
#ifndef DISABLE_COLOR
412
413
414
    /* Make sure we're using the buffer's associated colors, if
     * applicable. */
    color_init();
415
416
417
#endif

    /* Update the edit window. */
418
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
419
420
}

421
#ifndef DISABLE_MULTIBUFFER
422
423
424
/* Switch to the next file buffer if next_buf is TRUE.  Otherwise,
 * switch to the previous file buffer. */
void switch_to_prevnext_buffer(bool next_buf)
Chris Allegretta's avatar
Chris Allegretta committed
425
{
426
    assert(openfile != NULL);
427

428
429
    /* If only one file buffer is open, indicate it on the statusbar and
     * get out. */
430
    if (openfile == openfile->next) {
431
432
	statusbar(_("No more open file buffers"));
	return;
Chris Allegretta's avatar
Chris Allegretta committed
433
    }
434

435
436
    /* Switch to the next or previous file buffer, depending on the
     * value of next_buf. */
437
    openfile = next_buf ? openfile->next : openfile->prev;
438
439

#ifdef DEBUG
440
    fprintf(stderr, "filename is %s\n", openfile->filename);
441
442
#endif

443
    /* Update the screen to account for the current buffer. */
444
    display_buffer();
445

446
    /* Indicate the switch on the statusbar. */
447
    statusbar(_("Switched to %s"),
448
449
	((openfile->filename[0] == '\0') ? _("New Buffer") :
	openfile->filename));
450
451

#ifdef DEBUG
452
    dump_filestruct(openfile->current);
453
#endif
454
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
455
456
}

457
/* Switch to the previous entry in the openfile filebuffer. */
458
void switch_to_prev_buffer_void(void)
459
{
460
    switch_to_prevnext_buffer(FALSE);
461
}
462

463
/* Switch to the next entry in the openfile filebuffer. */
464
void switch_to_next_buffer_void(void)
465
{
466
    switch_to_prevnext_buffer(TRUE);
467
}
468

469
/* Delete an entry from the openfile filebuffer, and switch to the one
470
471
472
 * after it.  Return TRUE on success, or FALSE if there are no more open
 * file buffers. */
bool close_buffer(void)
473
{
474
    assert(openfile != NULL);
475

476
    /* If only one file buffer is open, get out. */
477
    if (openfile == openfile->next)
478
	return FALSE;
479

480
#if !defined(NANO_TINY) && !defined(DISABLE_NANORC)
481
    update_poshistory(openfile->filename, openfile->current->lineno, xplustabs() + 1);
482
#endif
483

484
    /* Switch to the next file buffer. */
485
    switch_to_next_buffer_void();
486

487
    /* Close the file buffer we had open before. */
488
    unlink_opennode(openfile->prev);
489

490
491
492
    /* If only one buffer is open now, show Exit in the help lines. */
    if (openfile == openfile->next)
	exitfunc->desc = exit_tag;
493

494
    return TRUE;
495
}
496
#endif /* !DISABLE_MULTIBUFFER */
497

498
499
500
501
502
/* 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. */
503
504
505
506
507
508
509
510
511
512
513
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;

514
    assert(filename != NULL);
515
516
517
518
519

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

    /* Okay, if we can't stat the path due to a component's
520
       permissions, just try the relative one. */
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
    if (full_filename == NULL
        || (stat(full_filename, &fileinfo) == -1 && stat(filename, &fileinfo2) != -1))
        full_filename = mallocstrcpy(NULL, filename);

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

    free(full_filename);
    return ans;
}

537
538
539
540
541
542
/* We make a new line of text from buf.  buf is length buf_len.  If
 * first_line_ins is TRUE, then we put the new line at the top of the
 * file.  Otherwise, we assume prevnode is the last line of the file,
 * and put our line after prevnode. */
filestruct *read_line(char *buf, filestruct *prevnode, bool
	*first_line_ins, size_t buf_len)
543
{
544
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
545

546
547
    /* Convert nulls to newlines.  buf_len is the string's real
     * length. */
548
    unsunder(buf, buf_len);
549

550
    assert(openfile->fileage != NULL && strlen(buf) == buf_len);
551

552
    fileptr->data = mallocstrcpy(NULL, buf);
553

554
#ifndef NANO_TINY
555
556
557
558
    /* 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';
559
#endif
560

561
#ifndef DISABLE_COLOR
562
	fileptr->multidata = NULL;
563
564
#endif

565
    if (*first_line_ins) {
566
567
568
	/* Special case: We're inserting with the cursor on the first
	 * line. */
	fileptr->prev = NULL;
569
	fileptr->next = openfile->fileage;
570
	fileptr->lineno = 1;
571
	if (*first_line_ins) {
572
573
574
575
	    *first_line_ins = FALSE;
	    /* If we're inserting into the first line of the file, then
	     * we want to make sure that our edit buffer stays on the
	     * first line and that fileage stays up to date. */
576
	    openfile->edittop = fileptr;
577
	} else
578
579
	    openfile->filebot = fileptr;
	openfile->fileage = fileptr;
580
581
582
583
584
585
586
    } else {
	assert(prevnode != NULL);

	fileptr->prev = prevnode;
	fileptr->next = NULL;
	fileptr->lineno = prevnode->lineno + 1;
	prevnode->next = fileptr;
587
588
    }

589
590
    return fileptr;
}
591

592
/* Read an open file into the current buffer.  f should be set to the
593
 * open file, and filename should be set to the name of the file.
594
595
596
 * 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. */
597
void read_file(FILE *f, int fd, const char *filename, bool undoable, bool checkwritable)
598
599
600
601
602
603
604
605
606
607
608
609
610
{
    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. */
611
    filestruct *fileptr = openfile->current;
612
613
614
615
616
617
	/* The current line of the file. */
    bool first_line_ins = FALSE;
	/* Whether we're inserting with the cursor on the first line. */
    int input_int;
	/* The current value we read from the file, whether an input
	 * character or EOF. */
618
619
    bool writable = TRUE;
	/* Is the file writable (if we care) */
620
#ifndef NANO_TINY
621
622
    int format = 0;
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
623
#endif
624

625
626
    assert(openfile->fileage != NULL && openfile->current != NULL);

627
628
    buf = charalloc(bufx);
    buf[0] = '\0';
629

630
631
632
633
634
#ifndef NANO_TINY
    if (undoable)
	add_undo(INSERT);
#endif

635
636
637
638
    if (openfile->current == openfile->fileage)
	first_line_ins = TRUE;
    else
	fileptr = openfile->current->prev;
639

640
    /* Read the entire file into the filestruct. */
641
642
643
644
645
646
    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') {
647
#ifndef NANO_TINY
648
649
650
651
652
653
654
655
656
	    /* If it's a DOS file or a DOS/Mac file ('\r' before '\n' on
	     * the first line if we think it's a *nix file, or on any
	     * line otherwise), and file conversion isn't disabled,
	     * handle it! */
	    if (!ISSET(NO_CONVERT) && (num_lines == 0 || format != 0) &&
		i > 0 && buf[i - 1] == '\r') {
		if (format == 0 || format == 2)
		    format++;
	    }
657
#endif
658

659
660
	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
661

662
663
664
	    /* Reset the line length in preparation for the next
	     * line. */
	    len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
665

666
667
668
	    num_lines++;
	    buf[0] = '\0';
	    i = 0;
669
#ifndef NANO_TINY
670
671
672
673
674
	/* 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') {
675
676
677
678
679
	    /* 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;
680

681
682
	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
683

684
685
686
687
	    /* 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
688

689
690
691
692
	    num_lines++;
	    buf[0] = input;
	    buf[1] = '\0';
	    i = 1;
693
#endif
694
695
696
697
	} else {
	    /* Calculate the total length of the line.  It might have
	     * nulls in it, so we can't just use strlen() here. */
	    len++;
698

699
700
701
702
703
704
705
706
	    /* 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);
707
	    }
708
709
710
711
712
713
714
715
716
717
718

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

    /* Perhaps this could use some better handling. */
    if (ferror(f))
	nperror(filename);
    fclose(f);
719
    if (fd > 0 && checkwritable) {
720
	close(fd);
721
722
	writable = is_file_writable(filename);
    }
723

724
#ifndef NANO_TINY
725
726
727
728
729
730
731
732
    /* 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';
    }
733
#endif
734

735
736
    /* Did we not get a newline and still have stuff to do? */
    if (len > 0) {
737
#ifndef NANO_TINY
738
739
740
741
742
743
744
	/* 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;
745
746
#endif

747
748
749
750
	/* Read in the last line properly. */
	fileptr = read_line(buf, fileptr, &first_line_ins, len);
	num_lines++;
    }
751

752
    free(buf);
753

754
    /* If we didn't get a file and we don't already have one, open a
755
     * blank buffer. */
756
    if (fileptr == NULL)
757
	open_buffer("", FALSE);
758

759
760
    /* Attach the file we got to the filestruct.  If we got a file of
     * zero bytes, don't do anything. */
761
    if (num_lines > 0) {
762
763
764
765
766
767
768
769
770
771
772
773
	/* 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
774
775
	    /* Tack the text at fileptr onto the beginning of the text
	     * at current. */
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
	    openfile->current->data =
		charealloc(openfile->current->data, len +
		current_len + 1);
	    charmove(openfile->current->data + len,
		openfile->current->data, current_len + 1);
	    strncpy(openfile->current->data, fileptr->data, len);

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
791
	    /* Move fileptr back one line and blow away the old fileptr,
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
	     * since its text has been saved. */
	    fileptr = fileptr->prev;
	    if (fileptr != NULL) {
		if (fileptr->next != NULL)
		    free(fileptr->next);
	    }
	}

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

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

811
812
    openfile->totsize += get_totsize(openfile->fileage,
	openfile->filebot);
813

814
    /* If the NO_NEWLINES flag isn't set, and text has been added to
815
     * the magicline (i.e. a file that doesn't end in a newline has been
816
817
818
     * 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') {
819
820
821
822
823
824
825
826
827
	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();

828
#ifndef NANO_TINY
829
830
831
    if (undoable)
	update_undo(INSERT);

832
833
834
    if (format == 3) {
	if (writable)
	    statusbar(
835
836
837
		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);
838
839
840
841
842
843
	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) {
844
	openfile->fmt = MAC_FILE;
845
846
	if (writable)
	    statusbar(P_("Read %lu line (Converted from Mac format)",
847
848
		"Read %lu lines (Converted from Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
849
850
851
852
	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);
853
    } else if (format == 1) {
854
	openfile->fmt = DOS_FILE;
855
856
	if (writable)
	    statusbar(P_("Read %lu line (Converted from DOS format)",
857
858
		"Read %lu lines (Converted from DOS format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
859
860
861
862
	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);
863
    } else
864
#endif
865
866
867
868
869
870
	if (writable)
	    statusbar(P_("Read %lu line", "Read %lu lines",
		(unsigned long)num_lines), (unsigned long)num_lines);
	else
	    statusbar(P_("Read %lu line ( Warning: No write permission)",
		"Read %lu lines (Warning: No write permission)",
871
872
		(unsigned long)num_lines), (unsigned long)num_lines);
}
Chris Allegretta's avatar
Chris Allegretta committed
873

874
875
876
877
/* 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".
 *
878
 * Return -2 if we say "New File", -1 if the file isn't opened, and the
879
 * fd opened otherwise.  The file might still have an error while reading
880
 * with a 0 return value.  *f is set to the opened file. */
881
882
int open_file(const char *filename, bool newfie, FILE **f)
{
883
    struct stat fileinfo, fileinfo2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
884
    int fd;
885
    char *full_filename;
886

887
    assert(filename != NULL && f != NULL);
888

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

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

898
899
900
901
902
903
904

#ifndef NANO_TINY
    if (ISSET(LOCKING))
        if (do_lockfile(full_filename) < 0)
            return -1;
#endif

905
    if (stat(full_filename, &fileinfo) == -1) {
906
907
	/* Well, maybe we can open the file even if the OS says it's
	 * not there. */
908
909
910
911
912
913
        if ((fd = open(filename, O_RDONLY)) != -1) {
	    statusbar(_("Reading File"));
	    free(full_filename);
	    return 0;
	}

914
915
916
917
918
	if (newfie) {
	    statusbar(_("New File"));
	    return -2;
	}
	statusbar(_("\"%s\" not found"), filename);
919
	beep();
920
921
	return -1;
    } else if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
922
	S_ISBLK(fileinfo.st_mode)) {
923
924
	/* Don't open directories, character files, or block files.
	 * Sorry, /dev/sndstat! */
925
926
	statusbar(S_ISDIR(fileinfo.st_mode) ?
		_("\"%s\" is a directory") :
927
		_("\"%s\" is a device file"), filename);
928
	beep();
929
	return -1;
930
931
932
    } else if ((fd = open(full_filename, O_RDONLY)) == -1) {
	statusbar(_("Error reading %s: %s"), filename,
		strerror(errno));
933
	beep();
934
	return -1;
935
     } else {
936
	/* The file is A-OK.  Open it. */
937
	*f = fdopen(fd, "rb");
938

939
940
941
	if (*f == NULL) {
	    statusbar(_("Error reading %s: %s"), filename,
		strerror(errno));
942
	    beep();
943
944
945
	    close(fd);
	} else
	    statusbar(_("Reading File"));
946
    }
947

948
949
    free(full_filename);

950
951
952
    return fd;
}

953
954
955
956
957
/* 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)
958
{
959
    static int ulmax_digits = -1;
960
961
962
    unsigned long i = 0;
    char *buf;
    size_t namelen, suffixlen;
963

964
    assert(name != NULL && suffix != NULL);
965

966
967
968
    if (ulmax_digits == -1)
	ulmax_digits = digits(ULONG_MAX);

969
970
    namelen = strlen(name);
    suffixlen = strlen(suffix);
Chris Allegretta's avatar
Chris Allegretta committed
971

972
    buf = charalloc(namelen + suffixlen + ulmax_digits + 2);
973
    sprintf(buf, "%s%s", name, suffix);
974

975
976
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
977

978
979
980
981
	if (stat(buf, &fs) == -1)
	    return buf;
	if (i == ULONG_MAX)
	    break;
982

983
984
985
	i++;
	sprintf(buf + namelen + suffixlen, ".%lu", i);
    }
Chris Allegretta's avatar
Chris Allegretta committed
986

987
988
989
    /* We get here only if there is no possible save file.  Blank out
     * the filename to indicate this. */
    null_at(&buf, 0);
990

991
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
992
993
}

994
995
996
/* 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. */
997
void do_insertfile(
998
#ifndef NANO_TINY
999
1000
1001
1002
1003
1004
1005
1006
1007
	bool execute
#else
	void
#endif
	)
{
    int i;
    const char *msg;
    char *ans = mallocstrcpy(NULL, "");
1008
	/* The last answer the user typed at the statusbar prompt. */
1009
    filestruct *edittop_save = openfile->edittop;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1010
    size_t current_x_save = openfile->current_x;
1011
    ssize_t current_y_save = openfile->current_y;
1012
    bool edittop_inside = FALSE, meta_key = FALSE, func_key = FALSE;
1013
#ifndef NANO_TINY
1014
    const sc *s;
1015
    bool right_side_up = FALSE, single_line = FALSE;
1016
#endif
1017

1018
1019
    currmenu = MINSERTFILE;

1020
    while (TRUE) {
1021
#ifndef NANO_TINY
1022
	if (execute) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1023
	    msg =
1024
#ifndef DISABLE_MULTIBUFFER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1025
		ISSET(MULTIBUFFER) ?
1026
		_("Command to execute in new buffer [from %s] ") :
1027
#endif
1028
		_("Command to execute [from %s] ");
1029
1030
	} else {
#endif
1031
	    msg =
1032
#ifndef DISABLE_MULTIBUFFER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1033
		ISSET(MULTIBUFFER) ?
1034
		_("File to insert into new buffer [from %s] ") :
1035
#endif
1036
		_("File to insert [from %s] ");
1037
#ifndef NANO_TINY
1038
1039
	}
#endif
1040

1041
	i = do_prompt(TRUE,
1042
1043
1044
#ifndef DISABLE_TABCOMP
		TRUE,
#endif
1045
#ifndef NANO_TINY
1046
		execute ? MEXTCMD :
1047
#endif
1048
		MINSERTFILE, ans,
1049
		&meta_key, &func_key,
1050
#ifndef NANO_TINY
1051
		NULL,
1052
#endif
1053
		edit_refresh, msg,
1054
#ifndef DISABLE_OPERATINGDIR
1055
1056
		operating_dir != NULL && strcmp(operating_dir,
		".") != 0 ? operating_dir :
1057
1058
#endif
		"./");
1059

1060
	/* If we're in multibuffer mode and the filename or command is
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1061
	 * blank, open a new buffer instead of canceling.  If the
1062
1063
	 * 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
1064
	if (i == -1 || ((i == -2 || *answer == '\n')
1065
#ifndef DISABLE_MULTIBUFFER
1066
1067
1068
1069
1070
1071
		&& !ISSET(MULTIBUFFER)
#endif
		)) {
	    statusbar(_("Cancelled"));
	    break;
	} else {
1072
	    size_t pww_save = openfile->placewewant;
1073

1074
	    ans = mallocstrcpy(ans, answer);
1075

1076
#ifndef NANO_TINY
1077
	    s = get_shortcut(currmenu, &i, &meta_key);
1078

1079
#ifndef DISABLE_MULTIBUFFER
1080
	    if (s && s->scfunc == new_buffer_void) {
1081
1082
1083
1084
1085
		/* Don't allow toggling if we're in view mode. */
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
		continue;
	    } else
1086
#endif
1087
	    if (s && s->scfunc == flip_execute_void) {
1088
1089
1090
1091
1092
1093
		execute = !execute;
		continue;
	    }
#ifndef DISABLE_BROWSER
	    else
#endif
1094
#endif /* !NANO_TINY */
1095

1096
#ifndef DISABLE_BROWSER
1097
	    if (s && s->scfunc == to_files_void) {
1098
		char *tmp = do_browse_from(answer);
1099

1100
1101
		if (tmp == NULL)
		    continue;
1102

1103
		/* We have a file now.  Indicate this. */
1104
1105
		free(answer);
		answer = tmp;
1106

1107
1108
1109
		i = 0;
	    }
#endif
1110

1111
1112
1113
	    /* If we don't have a file yet, go back to the statusbar
	     * prompt. */
	    if (i != 0
1114
#ifndef DISABLE_MULTIBUFFER
1115
1116
1117
1118
		&& (i != -2 || !ISSET(MULTIBUFFER))
#endif
		)
		continue;
1119

1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
#ifndef NANO_TINY
		/* Keep track of whether the mark begins inside the
		 * partition and will need adjustment. */
		if (openfile->mark_set) {
		    filestruct *top, *bot;
		    size_t top_x, bot_x;

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

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

1135
#ifndef DISABLE_MULTIBUFFER
1136
1137
1138
1139
	    if (!ISSET(MULTIBUFFER)) {
#endif
		/* If we're not inserting into a new buffer, partition
		 * the filestruct so that it contains no text and hence
1140
1141
1142
		 * looks like a new buffer, and keep track of whether
		 * the top of the edit window is inside the
		 * partition. */
1143
1144
1145
		filepart = partition_filestruct(openfile->current,
			openfile->current_x, openfile->current,
			openfile->current_x);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1146
1147
		edittop_inside =
			(openfile->edittop == openfile->fileage);
1148
#ifndef DISABLE_MULTIBUFFER
1149
1150
	    }
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1151

1152
1153
	    /* Convert newlines to nulls, just before we insert the file
	     * or execute the command. */
1154
	    sunder(answer);
1155
	    align(&answer);
1156

1157
#ifndef NANO_TINY
1158
	    if (execute) {
1159
#ifndef DISABLE_MULTIBUFFER
1160
		if (ISSET(MULTIBUFFER))
1161
		    /* Open a blank buffer. */
1162
		    open_buffer("", FALSE);
1163
1164
1165
#endif

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

1168
#ifndef DISABLE_MULTIBUFFER
1169
1170
1171
1172
1173
1174
1175
		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;
		}
1176
#endif
1177
1178
	    } else {
#endif /* !NANO_TINY */
1179
1180
		/* Make sure the path to the file specified in answer is
		 * tilde-expanded. */
1181
1182
		answer = mallocstrassn(answer,
			real_dir_from_tilde(answer));
1183
1184
1185

		/* Save the file specified in answer in the current
		 * buffer. */
1186
		open_buffer(answer, TRUE);
1187
#ifndef NANO_TINY
1188
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1189
#endif
1190

1191
#if !defined(DISABLE_MULTIBUFFER) && !defined(DISABLE_NANORC)
1192
	    if (ISSET(MULTIBUFFER)) {
1193
1194
		/* Update the screen to account for the current
		 * buffer. */
1195
		display_buffer();
1196

1197
#ifndef NANO_TINY
1198
		ssize_t savedposline, savedposcol;
1199
1200
1201
		if (!execute && ISSET(POS_HISTORY)
			&& check_poshistory(answer, &savedposline, &savedposcol))
		    do_gotolinecolumn(savedposline, savedposcol, FALSE, FALSE, FALSE, FALSE);
1202
#endif
1203
	    } else
Chris Allegretta's avatar
Chris Allegretta committed
1204
#endif
1205
	    {
1206
		filestruct *top_save = openfile->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
1207

1208
1209
1210
		/* 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
1211
		if (edittop_inside)
1212
		    edittop_save = openfile->fileage;
1213
1214

		/* Update the current x-coordinate to account for the
1215
1216
1217
1218
		 * number of characters inserted on the current line.
		 * If the mark begins inside the partition, adjust the
		 * mark coordinates to compensate for the change in the
		 * current line. */
1219
		openfile->current_x = strlen(openfile->filebot->data);
1220
1221
1222
1223
		if (openfile->fileage == openfile->filebot) {
#ifndef NANO_TINY
		    if (openfile->mark_set) {
			openfile->mark_begin = openfile->current;
1224
			if (!right_side_up)
1225
1226
1227
1228
			    openfile->mark_begin_x +=
				openfile->current_x;
		    }
#endif
1229
		    openfile->current_x += current_x_save;
1230
		}
1231
#ifndef NANO_TINY
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
		else if (openfile->mark_set) {
		    if (!right_side_up) {
			if (single_line) {
			    openfile->mark_begin = openfile->current;
			    openfile->mark_begin_x -= current_x_save;
			} else
			    openfile->mark_begin_x -=
				openfile->current_x;
		    }
		}
1242
#endif
1243
1244
1245

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

1248
1249
1250
1251
		/* 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. */
1252
		unpartition_filestruct(&filepart);
1253

1254
1255
1256
		/* Renumber starting with the beginning line of the old
		 * partition. */
		renumber(top_save);
1257

1258
		/* Restore the old edittop. */
1259
		openfile->edittop = edittop_save;
1260

1261
1262
1263
		/* Restore the old place we want. */
		openfile->placewewant = pww_save;

1264
1265
		/* Mark the file as modified. */
		set_modified();
1266

1267
1268
		/* Update the screen. */
		edit_refresh();
1269
	    }
1270

1271
1272
1273
1274
	    break;
	}
    }
    free(ans);
1275
1276
}

1277
1278
1279
/* 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. */
1280
void do_insertfile_void(void)
1281
{
1282
1283
1284
1285
1286
    if (ISSET(RESTRICTED)) {
        nano_disabled_msg();
	return;
    }

1287
#ifndef DISABLE_MULTIBUFFER
1288
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
1289
	statusbar(_("Key invalid in non-multibuffer mode"));
1290
1291
1292
    else
#endif
	do_insertfile(
1293
#ifndef NANO_TINY
1294
1295
1296
		FALSE
#endif
		);
1297
1298
1299
1300

    display_main_list();
}

1301
/* When passed "[relative path]" or "[relative path][filename]" in
1302
 * origpath, return "[full path]" or "[full path][filename]" on success,
1303
1304
1305
1306
 * 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. */
1307
char *get_full_path(const char *origpath)
1308
{
1309
1310
1311
1312
    struct stat fileinfo;
    char *d_here, *d_there, *d_there_file = NULL;
    const char *last_slash;
    bool path_only;
1313

1314
    if (origpath == NULL)
1315
	return NULL;
1316

1317
    /* Get the current directory.  If it doesn't exist, back up and try
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1318
1319
     * again until we get a directory that does, and use that as the
     * current directory. */
1320
1321
    d_here = charalloc(PATH_MAX + 1);
    d_here = getcwd(d_here, PATH_MAX + 1);
1322

1323
    while (d_here == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1324
1325
1326
	if (chdir("..") == -1)
	    break;

1327
1328
1329
1330
1331
	d_here = getcwd(d_here, PATH_MAX + 1);
    }

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

1334
1335
	/* If the current directory isn't "/", tack a slash onto the end
	 * of it. */
1336
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1337
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1338
1339
	    strcat(d_here, "/");
	}
1340
1341
1342
    /* Otherwise, set d_here to "". */
    } else
	d_here = mallocstrcpy(NULL, "");
1343

1344
    d_there = real_dir_from_tilde(origpath);
1345

1346
1347
1348
    /* If stat()ing d_there fails, assume that d_there refers to a new
     * file that hasn't been saved to disk yet.  Set path_only to TRUE
     * if d_there refers to a directory, and FALSE otherwise. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1349
1350
    path_only = (stat(d_there, &fileinfo) != -1 &&
	S_ISDIR(fileinfo.st_mode));
1351

1352
1353
1354
    /* If path_only is TRUE, make sure d_there ends in a slash. */
    if (path_only) {
	size_t d_there_len = strlen(d_there);
1355

1356
1357
1358
	if (d_there[d_there_len - 1] != '/') {
	    d_there = charealloc(d_there, d_there_len + 2);
	    strcat(d_there, "/");
1359
	}
1360
    }
1361

1362
1363
    /* Search for the last slash in d_there. */
    last_slash = strrchr(d_there, '/');
1364

1365
1366
1367
1368
    /* 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);
1369

1370
1371
1372
1373
1374
1375
1376
1377
	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);

1378
	/* Remove the filename portion of the answer from d_there. */
1379
1380
	null_at(&d_there, last_slash - d_there + 1);

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1381
	/* Go to the path specified in d_there. */
1382
1383
1384
	if (chdir(d_there) == -1) {
	    free(d_there);
	    d_there = NULL;
1385
	} else {
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
	    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()
1410
	     * error, since we can do nothing if we get one. */
1411
	    IGNORE_CALL_RESULT(chdir(d_here));
1412

1413
1414
	    /* Free d_here, since we're done using it. */
	    free(d_here);
1415
	}
1416
    }
1417

1418
1419
1420
1421
1422
1423
1424
    /* 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) +
1425
		strlen(d_there_file) + 1);
1426
1427
	strcat(d_there, d_there_file);
    }
1428

1429
1430
    /* Free d_there_file, since we're done using it. */
    if (d_there_file != NULL)
1431
1432
	free(d_there_file);

1433
    return d_there;
1434
}
1435

1436
1437
1438
/* Return the full version of path, as returned by get_full_path().  On
 * error, if path doesn't reference a directory, or if the directory
 * isn't writable, return NULL. */
1439
char *check_writable_directory(const char *path)
1440
{
1441
1442
    char *full_path = get_full_path(path);

1443
    /* If get_full_path() fails, return NULL. */
1444
    if (full_path == NULL)
1445
	return NULL;
1446

1447
1448
1449
    /* If we can't write to path or path isn't a directory, return
     * NULL. */
    if (access(full_path, W_OK) != 0 ||
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1450
	full_path[strlen(full_path) - 1] != '/') {
1451
	free(full_path);
1452
	return NULL;
1453
    }
1454

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1455
    /* Otherwise, return the full path. */
1456
1457
1458
    return full_path;
}

1459
1460
/* This function calls mkstemp(($TMPDIR|P_tmpdir|/tmp/)"nano.XXXXXX").
 * On success, it returns the malloc()ed filename and corresponding FILE
1461
 * stream, opened in "r+b" mode.  On error, it returns NULL for the
1462
1463
 * filename and leaves the FILE stream unchanged. */
char *safe_tempfile(FILE **f)
1464
{
1465
    char *full_tempdir = NULL;
1466
1467
1468
    const char *tmpdir_env;
    int fd;
    mode_t original_umask = 0;
1469

1470
1471
    assert(f != NULL);

1472
1473
1474
    /* 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. */
1475
    tmpdir_env = getenv("TMPDIR");
1476
    if (tmpdir_env != NULL)
1477
	full_tempdir = check_writable_directory(tmpdir_env);
1478

1479
1480
    /* If $TMPDIR is unset, empty, or not a writable directory, and
     * full_tempdir is NULL, try P_tmpdir instead. */
1481
    if (full_tempdir == NULL)
1482
	full_tempdir = check_writable_directory(P_tmpdir);
1483

1484
1485
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
1486
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1487

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1488
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1489
    strcat(full_tempdir, "nano.XXXXXX");
1490

1491
1492
1493
1494
1495
1496
1497
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

    if (fd != -1)
	*f = fdopen(fd, "r+b");
1498
1499
1500
    else {
	free(full_tempdir);
	full_tempdir = NULL;
1501
    }
1502

1503
1504
    umask(original_umask);

1505
    return full_tempdir;
1506
}
1507
1508

#ifndef DISABLE_OPERATINGDIR
1509
1510
1511
1512
1513
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1514
    if (operating_dir == NULL)
1515
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1516

1517
1518
1519
    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
1520
     * inaccessible, unset operating_dir. */
1521
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1522
1523
1524
1525
1526
1527
1528
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1529
1530
1531
1532
1533
/* 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)
1534
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1535
1536
1537
1538
    /* 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. */
1539

1540
    char *fullpath;
1541
    bool retval = FALSE;
1542
    const char *whereami1, *whereami2 = NULL;
1543

1544
    /* If no operating directory is set, don't bother doing anything. */
1545
    if (operating_dir == NULL)
1546
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1547

1548
    assert(full_operating_dir != NULL);
1549
1550

    fullpath = get_full_path(currpath);
1551

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1552
1553
1554
    /* 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
1555
     * non-existent directory as being outside the operating directory,
1556
     * so we return FALSE.  If allow_tabcomp is TRUE, then currpath
1557
1558
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1559
    if (fullpath == NULL)
1560
	return allow_tabcomp;
1561
1562
1563
1564
1565

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

1566
    /* If both searches failed, we're outside the operating directory.
1567
     * Otherwise, check the search results.  If the full operating
1568
1569
1570
     * 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. */
1571
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1572
1573
	retval = TRUE;
    free(fullpath);
1574
1575

    /* Otherwise, we're still inside it. */
1576
    return retval;
1577
}
1578
1579
#endif

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

1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
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;
    }
}
1618
#endif /* !NANO_TINY */
1619

1620
/* Read from inn, write to out.  We assume inn is opened for reading,
1621
1622
 * and out for writing.  We return 0 on success, -1 on read error, or -2
 * on write error. */
1623
1624
int copy_file(FILE *inn, FILE *out)
{
1625
    int retval = 0;
1626
    char buf[BUFSIZ];
1627
1628
    size_t charsread;

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

1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
    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
1642

1643
1644
1645
1646
    if (fclose(inn) == EOF)
	retval = -1;
    if (fclose(out) == EOF)
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1647

1648
1649
1650
    return retval;
}

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

1698
    assert(name != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1699

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1700
    if (*name == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1701
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1702

1703
1704
1705
    if (f_open != NULL)
	f = f_open;

1706
1707
    if (!tmp)
	titlebar(NULL);
1708

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1709
    realname = real_dir_from_tilde(name);
1710

1711
#ifndef DISABLE_OPERATINGDIR
1712
    /* If we're writing a temporary file, we're probably going outside
1713
     * the operating directory, so skip the operating directory test. */
1714
    if (!tmp && check_operating_dir(realname, FALSE)) {
1715
1716
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1717
1718
1719
    }
#endif

1720
    anyexists = (lstat(realname, &lst) != -1);
1721

1722
1723
    /* If the temp file exists and isn't already open, give up. */
    if (tmp && anyexists && f_open == NULL)
1724
	goto cleanup_and_exit;
1725

1726
1727
1728
    /* 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)) {
1729
1730
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1731
1732
1733
	goto cleanup_and_exit;
    }

1734
#ifndef NANO_TINY
1735
    /* Check whether the file (at the end of the symlink) exists. */
1736
    realexists = (stat(realname, &st) != -1);
1737

1738
1739
1740
1741
    /* 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. */
1742
1743
    if (openfile->current_stat == NULL && !tmp && realexists) {
	openfile->current_stat = (struct stat *)nmalloc(sizeof(struct stat));
1744
	stat(realname, openfile->current_stat);
1745
    }
1746

1747
    /* We backup only if the backup toggle is set, the file isn't
1748
1749
1750
1751
     * 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. */
1752
    if (ISSET(BACKUP_FILE) && !tmp && realexists && ((append !=
1753
1754
	OVERWRITE || openfile->mark_set) || (openfile->current_stat &&
	openfile->current_stat->st_mtime == st.st_mtime))) {
1755
	int backup_fd;
1756
	FILE *backup_file;
1757
	char *backupname;
1758
	struct utimbuf filetime;
1759
	int copy_status;
1760
	int backup_cflags;
1761

1762
	/* Save the original file's access and modification times. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1763
1764
	filetime.actime = openfile->current_stat->st_atime;
	filetime.modtime = openfile->current_stat->st_mtime;
1765

1766
1767
1768
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1769

1770
1771
1772
	    if (f == NULL) {
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1773
		beep();
1774
1775
1776
1777
		/* 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;
1778
	    }
1779
1780
	}

1781
	/* If backup_dir is set, we set backupname to
1782
1783
1784
1785
	 * 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]. */
1786
	if (backup_dir != NULL) {
1787
	    char *backuptemp = get_full_path(realname);
1788

1789
	    if (backuptemp == NULL)
1790
1791
1792
1793
1794
1795
		/* 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~. */
1796
		backuptemp = mallocstrcpy(NULL, tail(realname));
1797
	    else {
1798
1799
		size_t i = 0;

1800
1801
1802
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1803
1804
1805
1806
		}
	    }

	    backupname = charalloc(strlen(backup_dir) +
1807
1808
1809
1810
		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
1811
	    if (*backuptemp == '\0') {
1812
		statusbar(_("Error writing backup file %s: %s"), backupname,
1813
1814
1815
		    _("Too many backup files?"));
		free(backuptemp);
		free(backupname);
1816
1817
1818
1819
1820
		/* 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! */
1821
		goto cleanup_and_exit;
1822
1823
1824
1825
	    } else {
		free(backupname);
		backupname = backuptemp;
	    }
1826
1827
1828
1829
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1830

1831
	/* First, unlink any existing backups.  Next, open the backup
1832
1833
	 * file with O_CREAT and O_EXCL.  If it succeeds, we have a file
	 * descriptor to a new backup file. */
1834
	if (unlink(backupname) < 0 && errno != ENOENT && !ISSET(INSECURE_BACKUP)) {
1835
1836
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1837
1838
	    statusbar(_("Error writing backup file %s: %s"), backupname,
			strerror(errno));
1839
1840
1841
1842
	    free(backupname);
	    goto cleanup_and_exit;
	}

1843
1844
1845
1846
1847
1848
	if (ISSET(INSECURE_BACKUP))
	    backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
        else
	    backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

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

1854
1855
1856
1857
1858
1859
	if (backup_fd < 0 || backup_file == NULL) {
	    statusbar(_("Error writing backup file %s: %s"), backupname,
			strerror(errno));
	    free(backupname);
	    goto cleanup_and_exit;
	}
1860

1861
1862
1863
        /* We shouldn't worry about chown()ing something if we're not
	   root, since it's likely to fail! */
	if (geteuid() == NANO_ROOT_UID && fchown(backup_fd,
1864
		openfile->current_stat->st_uid, openfile->current_stat->st_gid) == -1
1865
1866
1867
		&& !ISSET(INSECURE_BACKUP)) {
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1868
1869
1870
1871
1872
1873
1874
	    statusbar(_("Error writing backup file %s: %s"), backupname,
		strerror(errno));
	    free(backupname);
	    fclose(backup_file);
	    goto cleanup_and_exit;
	}

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

#ifdef DEBUG
1891
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1892
1893
#endif

1894
1895
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1896

1897
1898
	if (copy_status != 0) {
	    statusbar(_("Error reading %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1899
			strerror(errno));
1900
1901
1902
1903
1904
1905
	    beep();
	    goto cleanup_and_exit;
	}

	/* And set its metadata. */
	if (utime(backupname, &filetime) == -1 && !ISSET(INSECURE_BACKUP)) {
1906
1907
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1908
	    statusbar(_("Error writing backup file %s: %s"), backupname,
1909
			strerror(errno));
1910
1911
1912
1913
	    /* 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! */
1914
	    goto cleanup_and_exit;
1915
	}
1916

1917
1918
	free(backupname);
    }
1919

1920
    skip_backup:
1921
#endif /* !NANO_TINY */
1922

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1923
1924
    /* 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
1925
1926
1927
1928
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1929
	goto cleanup_and_exit;
1930
    }
1931

1932
1933
    if (f_open == NULL) {
	original_umask = umask(0);
1934

1935
	/* If we create a temp file, we don't let anyone else access it.
1936
1937
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1938
	    umask(S_IRWXG | S_IRWXO);
1939
1940
	else
	    umask(original_umask);
1941
    }
1942

1943
    /* If we're prepending, copy the file to a temp file. */
1944
    if (append == PREPEND) {
1945
1946
1947
	int fd_source;
	FILE *f_source = NULL;

1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
	if (f == NULL) {
	    f = fopen(realname, "rb");

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

1959
	tempname = safe_tempfile(&f);
1960

1961
	if (tempname == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1962
	    statusbar(_("Error writing temp file: %s"),
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1963
		strerror(errno));
1964
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1965
	}
1966

1967
	if (f_open == NULL) {
1968
	    fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1969

1970
1971
1972
1973
1974
	    if (fd_source != -1) {
		f_source = fdopen(fd_source, "rb");
		if (f_source == NULL) {
		    statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1975
		    beep();
1976
1977
1978
1979
1980
1981
		    close(fd_source);
		    fclose(f);
		    unlink(tempname);
		    goto cleanup_and_exit;
		}
	    }
1982
1983
1984
	}

	if (copy_file(f_source, f) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1985
1986
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1987
	    unlink(tempname);
1988
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1989
1990
1991
	}
    }

1992
1993
1994
    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
1995
1996
1997
	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);
1998

1999
2000
	/* Set the umask back to the user's original value. */
	umask(original_umask);
2001

2002
2003
2004
2005
	/* If we couldn't open the file, give up. */
	if (fd == -1) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
2006

2007
2008
2009
2010
2011
	    /* tempname has been set only if we're prepending. */
	    if (tempname != NULL)
		unlink(tempname);
	    goto cleanup_and_exit;
	}
2012

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

2015
2016
2017
2018
2019
2020
	if (f == NULL) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
	    close(fd);
	    goto cleanup_and_exit;
	}
2021
2022
    }

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

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

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

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

2035
2036
	/* Convert nulls to newlines.  data_len is the string's real
	 * length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2037
2038
	unsunder(fileptr->data, data_len);

2039
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2040
2041
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
2042
	    fclose(f);
2043
	    goto cleanup_and_exit;
2044
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2045

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2046
	/* If we're on the last line of the file, don't write a newline
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2047
2048
2049
2050
2051
2052
2053
	 * 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 {
2054
#ifndef NANO_TINY
2055
2056
2057
2058
	    if (openfile->fmt == DOS_FILE || openfile->fmt ==
		MAC_FILE) {
		if (putc('\r', f) == EOF) {
		    statusbar(_("Error writing %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2059
			strerror(errno));
2060
2061
2062
		    fclose(f);
		    goto cleanup_and_exit;
		}
2063
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2064

2065
	    if (openfile->fmt != MAC_FILE) {
2066
#endif
2067
2068
		if (putc('\n', f) == EOF) {
		    statusbar(_("Error writing %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2069
			strerror(errno));
2070
2071
2072
		    fclose(f);
		    goto cleanup_and_exit;
		}
2073
#ifndef NANO_TINY
2074
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2075
#endif
2076
	}
Chris Allegretta's avatar
Chris Allegretta committed
2077
2078
2079
2080
2081

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

2082
    /* If we're prepending, open the temp file, and append it to f. */
2083
    if (append == PREPEND) {
2084
2085
2086
	int fd_source;
	FILE *f_source = NULL;

2087
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
2088

2089
2090
2091
2092
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
2093
	}
2094

2095
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2096
2097
	    statusbar(_("Error reading %s: %s"), tempname,
		strerror(errno));
2098
	    beep();
2099
	    fclose(f);
2100
	    goto cleanup_and_exit;
2101
2102
	}

2103
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2104
2105
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
2106
	    goto cleanup_and_exit;
2107
	}
2108
2109
2110
2111
2112
    } else if (fclose(f) != 0) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
	    goto cleanup_and_exit;
    }
2113

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

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

2133
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2134
2135
2136
2137
	/* 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));
2138
2139
	if (!openfile->mark_set)
	    stat(realname, openfile->current_stat);
2140
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2141

2142
2143
	statusbar(P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten),
2144
		(unsigned long)lineswritten);
2145
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2146
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2147
    }
2148

2149
    retval = TRUE;
2150
2151
2152

  cleanup_and_exit:
    free(realname);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2153
2154
    if (tempname != NULL)
	free(tempname);
2155

2156
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2157
2158
}

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

2173
2174
    assert(openfile->mark_set);

2175
2176
2177
    /* Partition the filestruct so that it contains only the marked
     * text. */
    mark_order((const filestruct **)&top, &top_x,
2178
	(const filestruct **)&bot, &bot_x, NULL);
2179
    filepart = partition_filestruct(top, top_x, bot, bot_x);
2180

2181
2182
2183
2184
2185
2186
    /* Handle the magicline if the NO_NEWLINES flag isn't set.  If the
     * line at filebot is blank, treat it as the magicline and hence the
     * end of the file.  Otherwise, add a magicline and treat it as the
     * end of the file. */
    if (!ISSET(NO_NEWLINES) &&
	(added_magicline = (openfile->filebot->data[0] != '\0')))
2187
	new_magicline();
2188

2189
    retval = write_file(name, f_open, tmp, append, TRUE);
2190

2191
2192
2193
    /* If the NO_NEWLINES flag isn't set, and we added a magicline,
     * remove it now. */
    if (!ISSET(NO_NEWLINES) && added_magicline)
2194
2195
2196
2197
	remove_magicline();

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

2200
    if (old_modified)
2201
2202
2203
2204
	set_modified();

    return retval;
}
2205

2206
#endif /* !NANO_TINY */
2207

2208
2209
2210
/* Write the current file to disk.  If the mark is on, write the current
 * marked selection to disk.  If exiting is TRUE, write the file to disk
 * regardless of whether the mark is on, and without prompting if the
2211
2212
 * TEMP_FILE flag is set.  Return TRUE on success or FALSE on error. */
bool do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
2213
{
2214
    int i;
2215
    append_type append = OVERWRITE;
2216
    char *ans;
2217
	/* The last answer the user typed at the statusbar prompt. */
2218
#ifndef DISABLE_EXTRA
2219
    static bool did_credits = FALSE;
2220
#endif
2221
2222
    bool retval = FALSE, meta_key = FALSE, func_key = FALSE;
    const sc *s;
Chris Allegretta's avatar
Chris Allegretta committed
2223

2224
    currmenu = MWRITEFILE;
2225

2226
    if (exiting && openfile->filename[0] != '\0' && ISSET(TEMP_FILE)) {
2227
	retval = write_file(openfile->filename, NULL, FALSE, OVERWRITE,
2228
		FALSE);
2229
2230

	/* Write succeeded. */
2231
	if (retval)
2232
	    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2233
2234
    }

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

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

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

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

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

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

2288
2289
	/* 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
2290
	if (i < 0 || *answer == '\n') {
2291
	    statusbar(_("Cancelled"));
2292
	    retval = FALSE;
2293
2294
2295
	    break;
	} else {
	    ans = mallocstrcpy(ans, answer);
2296
	    s = get_shortcut(currmenu, &i, &meta_key);
Chris Allegretta's avatar
Chris Allegretta committed
2297

2298
#ifndef DISABLE_BROWSER
2299
	    if (s && s->scfunc == to_files_void) {
2300
		char *tmp = do_browse_from(answer);
2301

2302
2303
		if (tmp == NULL)
		    continue;
2304

2305
		/* We have a file now.  Indicate this. */
2306
2307
2308
		free(answer);
		answer = tmp;
	    } else
2309
#endif /* !DISABLE_BROWSER */
2310
#ifndef NANO_TINY
2311
	    if (s && s->scfunc == dos_format_void) {
2312
2313
		openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE :
			DOS_FILE;
2314
		continue;
2315
	    } else if (s && s->scfunc == mac_format_void) {
2316
2317
		openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE :
			MAC_FILE;
2318
		continue;
2319
	    } else if (s && s->scfunc == backup_file_void) {
2320
2321
2322
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
2323
#endif /* !NANO_TINY */
2324
	    if (s && s->scfunc == prepend_void) {
2325
		append = (append == PREPEND) ? OVERWRITE : PREPEND;
2326
		continue;
2327
	    } else if (s && s->scfunc == append_void) {
2328
		append = (append == APPEND) ? OVERWRITE : APPEND;
2329
		continue;
2330
	    } else if (s && s->scfunc == do_help_void) {
2331
		continue;
2332
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2333

Chris Allegretta's avatar
Chris Allegretta committed
2334
#ifdef DEBUG
2335
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
2336
#endif
2337

2338
#ifndef DISABLE_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2339
2340
2341
2342
2343
2344
2345
2346
	    /* If the current file has been modified, we've pressed
	     * Ctrl-X at the edit window to exit, we've pressed "y" at
	     * the "Save modified buffer" prompt to save, we've entered
	     * "zzy" as the filename to save under (hence "xyzzy"), and
	     * this is the first time we've done this, show an Easter
	     * egg.  Display the credits. */
	    if (!did_credits && exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0) {
2347
		do_credits();
2348
		did_credits = TRUE;
2349
		retval = FALSE;
2350
2351
		break;
	    }
2352
#endif
2353

2354
	    if (append == OVERWRITE) {
2355
		size_t answer_len = strlen(answer);
2356
		bool name_exists, do_warning;
2357
		char *full_answer, *full_filename;
2358
2359
		struct stat st;

2360
2361
2362
		/* Convert newlines to nulls, just before we get the
		 * full path. */
		sunder(answer);
2363

2364
2365
		full_answer = get_full_path(answer);
		full_filename = get_full_path(openfile->filename);
2366
2367
		name_exists = (stat((full_answer == NULL) ? answer :
			full_answer, &st) != -1);
2368
2369
2370
2371
2372
2373
		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);
2374

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2375
2376
2377
2378
		/* Convert nulls to newlines.  answer_len is the
		 * string's real length. */
		unsunder(answer, answer_len);

2379
2380
2381
2382
		if (full_filename != NULL)
		    free(full_filename);
		if (full_answer != NULL)
		    free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2383

2384
		if (do_warning) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2385
2386
2387
2388
2389
2390
2391
2392
		    /* 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;
2393

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

2423
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2424

2425
2426
2427
2428
2429
	    /* Convert newlines to nulls, just before we save the
	     * file. */
	    sunder(answer);
	    align(&answer);

2430
	    /* Here's where we allow the selected text to be written to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2431
2432
2433
	     * 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. */
2434
2435
2436
2437
	    retval =
#ifndef NANO_TINY
		(!ISSET(RESTRICTED) && !exiting && openfile->mark_set) ?
		write_marked_file(answer, NULL, FALSE, append) :
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2438
#endif
2439
		write_file(answer, NULL, FALSE, append, FALSE);
2440

2441
2442
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2443
    }
2444
2445

    free(ans);
2446

2447
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2448
2449
}

2450
2451
/* Write the current file to disk.  If the mark is on, write the current
 * marked selection to disk. */
2452
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
2453
{
2454
    do_writeout(FALSE);
2455
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
2456
}
Chris Allegretta's avatar
Chris Allegretta committed
2457

2458
2459
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
2460
char *real_dir_from_tilde(const char *buf)
2461
{
2462
    char *retval;
2463

2464
    assert(buf != NULL);
2465

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2466
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2467
	size_t i = 1;
2468
	char *tilde_dir;
2469

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

2474
2475
2476
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
2477
	    tilde_dir = mallocstrcpy(NULL, homedir);
2478
2479
2480
	} else {
	    const struct passwd *userdata;

2481
2482
2483
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

2484
2485
	    do {
		userdata = getpwent();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2486
2487
	    } while (userdata != NULL && strcmp(userdata->pw_name,
		tilde_dir + 1) != 0);
2488
	    endpwent();
2489
	    if (userdata != NULL)
2490
		tilde_dir = mallocstrcpy(tilde_dir, userdata->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2491
	}
2492

2493
2494
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2495

2496
2497
2498
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2499

2500
    return retval;
2501
2502
}

2503
#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
2504
/* Our sort routine for file listings.  Sort alphabetically and
2505
 * case-insensitively, and sort directories before filenames. */
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
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;

2519
2520
2521
    /* 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
2522
     * have to use multibyte strcasecmp() instead. */
2523
    return mbstrcasecmp(a, b);
2524
}
2525
2526
2527
2528
2529

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

2532
2533
2534
2535
    for (; len > 0; len--)
	free(array[len - 1]);
    free(array);
}
2536
2537
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2538
#ifndef DISABLE_TABCOMP
2539
/* Is the given path a directory? */
2540
bool is_dir(const char *buf)
2541
{
2542
    char *dirptr;
2543
    struct stat fileinfo;
2544
2545
2546
    bool retval;

    assert(buf != NULL);
2547

2548
2549
2550
2551
    dirptr = real_dir_from_tilde(buf);

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

2553
    free(dirptr);
2554

2555
    return retval;
2556
}
Chris Allegretta's avatar
Chris Allegretta committed
2557

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2558
2559
2560
/* 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
2561
 * file, with the copyright years updated:
Chris Allegretta's avatar
Chris Allegretta committed
2562
2563
2564
 *
 * Termios command line History and Editting, originally
 * intended for NetBSD sh (ash)
2565
 * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
Chris Allegretta's avatar
Chris Allegretta committed
2566
2567
2568
2569
2570
2571
2572
2573
 *      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.
2574
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
2575

2576
/* We consider the first buf_len characters of buf for ~username tab
2577
2578
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
2579
	size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2580
{
2581
2582
    char **matches = NULL;
    const struct passwd *userdata;
2583

2584
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2585

2586
    *num_matches = 0;
2587

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

2593
2594
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2595
2596
2597
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2598
2599
#endif

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2600
2601
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
2602
2603
2604
	    matches[*num_matches] =
		charalloc(strlen(userdata->pw_name) + 2);
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2605
	    ++(*num_matches);
2606
	}
2607
2608
    }
    endpwent();
2609

2610
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2611
2612
}

2613
/* We consider the first buf_len characters of buf for filename tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2614
 * completion. */
2615
char **cwd_tab_completion(const char *buf, bool allow_files, size_t
2616
	*num_matches, size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2617
{
2618
    char *dirname = mallocstrcpy(NULL, buf), *filename;
2619
2620
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2621
    DIR *dir;
2622
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2623

2624
    assert(dirname != NULL && num_matches != NULL);
2625

2626
    *num_matches = 0;
2627
    null_at(&dirname, buf_len);
2628
2629
2630
2631
2632
2633
2634
2635

    /* 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
2636
	*tmpdirname = '\0';
2637
2638
2639
	tmpdirname = dirname;
	dirname = real_dir_from_tilde(dirname);
	free(tmpdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2640
    } else {
2641
2642
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2643
2644
    }

2645
    assert(dirname[strlen(dirname) - 1] == '/');
2646

Chris Allegretta's avatar
Chris Allegretta committed
2647
    dir = opendir(dirname);
2648

2649
    if (dir == NULL) {
2650
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2651
	beep();
2652
2653
2654
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2655
    }
2656
2657
2658

    filenamelen = strlen(filename);

2659
    while ((nextdir = readdir(dir)) != NULL) {
2660
2661
	bool skip_match = FALSE;

Chris Allegretta's avatar
Chris Allegretta committed
2662
#ifdef DEBUG
2663
	fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2664
#endif
2665
	/* See if this matches. */
2666
	if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
2667
2668
		(*filename == '.' || (strcmp(nextdir->d_name, ".") !=
		0 && strcmp(nextdir->d_name, "..") != 0))) {
2669
2670
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2671

2672
2673
2674
2675
	    char *tmp = charalloc(strlen(dirname) +
		strlen(nextdir->d_name) + 1);
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

2676
2677
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2678
2679
2680
2681
2682
2683
2684
	     * 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. */
2685
	    if (!allow_files && !is_dir(tmp))
2686
2687
2688
		skip_match = TRUE;

	    free(tmp);
2689

2690
	    if (skip_match)
2691
		continue;
2692

2693
2694
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
2695
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2696
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2697
2698
	}
    }
2699

2700
2701
    closedir(dir);
    free(dirname);
2702
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2703

2704
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2705
2706
}

2707
/* Do tab completion.  place refers to how much the statusbar cursor
2708
2709
 * position should be advanced.  refresh_func is the function we will
 * call to refresh the edit window. */
2710
char *input_tab(char *buf, bool allow_files, size_t *place, bool
2711
	*lastwastab, void (*refresh_func)(void), bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2712
{
2713
    size_t num_matches = 0, buf_len;
2714
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2715

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2718
    *list = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2719

2720
2721
    /* 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
2722
    if (*place > 0 && *buf == '~') {
2723
	const char *bob = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2724

2725
2726
2727
2728
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2729

2730
2731
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
2732
2733
	matches = cwd_tab_completion(buf, allow_files, &num_matches,
		*place);
2734

2735
2736
2737
    buf_len = strlen(buf);

    if (num_matches == 0 || *place != buf_len)
2738
2739
2740
2741
	beep();
    else {
	size_t match, common_len = 0;
	char *mzero;
2742
2743
2744
	const char *lastslash = revstrstr(buf, "/", buf + *place);
	size_t lastslash_len = (lastslash == NULL) ? 0 :
		lastslash - buf + 1;
2745
2746
2747
	char *match1_mb = charalloc(mb_cur_max() + 1);
	char *match2_mb = charalloc(mb_cur_max() + 1);
	int match1_mb_len, match2_mb_len;
2748
2749
2750

	while (TRUE) {
	    for (match = 1; match < num_matches; match++) {
2751
2752
		/* Get the number of single-byte characters that all the
		 * matches have in common. */
2753
		match1_mb_len = parse_mbchar(matches[0] + common_len,
2754
			match1_mb, NULL);
2755
		match2_mb_len = parse_mbchar(matches[match] +
2756
			common_len, match2_mb, NULL);
2757
2758
2759
		match1_mb[match1_mb_len] = '\0';
		match2_mb[match2_mb_len] = '\0';
		if (strcmp(match1_mb, match2_mb) != 0)
2760
2761
		    break;
	    }
2762

2763
	    if (match < num_matches || matches[0][common_len] == '\0')
2764
		break;
2765

2766
	    common_len += parse_mbchar(buf + common_len, NULL, NULL);
2767
	}
2768

2769
2770
2771
	free(match1_mb);
	free(match2_mb);

2772
	mzero = charalloc(lastslash_len + common_len + 1);
2773
2774
2775

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

2777
	common_len += lastslash_len;
2778
	mzero[common_len] = '\0';
2779

2780
	assert(common_len >= *place);
2781

2782
	if (num_matches == 1 && is_dir(mzero)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2783
	    mzero[common_len++] = '/';
2784

2785
2786
	    assert(common_len > *place);
	}
2787

2788
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2789
	    beep();
2790

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2791
	/* If there is more of a match to display on the statusbar, show
2792
	 * it.  We reset lastwastab to FALSE: it requires pressing Tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2793
	 * twice in succession with no statusbar changes to see a match
2794
2795
2796
	 * list. */
	if (common_len != *place) {
	    *lastwastab = FALSE;
2797
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2798
2799
	    charmove(buf + common_len, buf + *place, buf_len -
		*place + 1);
2800
	    strncpy(buf, mzero, common_len);
2801
	    *place = common_len;
2802
	} else if (!*lastwastab || num_matches < 2)
2803
2804
	    *lastwastab = TRUE;
	else {
2805
	    int longest_name = 0, ncols, editline = 0;
2806

2807
2808
2809
2810
2811
2812
2813
2814
	    /* 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
2815

2816
2817
		if (common_len > COLS - 1) {
		    longest_name = COLS - 1;
2818
2819
		    break;
		}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2820

2821
2822
		if (common_len > longest_name)
		    longest_name = common_len;
2823
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2824

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

2827
	    /* Each column will be (longest_name + 2) columns wide, i.e.
2828
2829
	     * two spaces between columns, except that there will be
	     * only one space after the last column. */
2830
	    ncols = (COLS + 1) / (longest_name + 2);
2831

2832
2833
2834
2835
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2836

2837
2838
	    /* Disable el cursor. */
	    curs_set(0);
2839

2840
2841
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2842

2843
		wmove(edit, editline, (longest_name + 2) *
2844
			(match % ncols));
2845

2846
		if (match % ncols == 0 &&
2847
			editline == editwinrows - 1 &&
2848
			num_matches - match > ncols) {
2849
2850
2851
		    waddstr(edit, _("(more)"));
		    break;
		}
2852

2853
2854
2855
2856
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2857

2858
		if ((match + 1) % ncols == 0)
2859
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2860
	    }
2861

2862
	    wnoutrefresh(edit);
2863
	    *list = TRUE;
2864
2865
2866
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2867
2868
    }

2869
    free_chararray(matches, num_matches);
2870

2871
    /* Only refresh the edit window if we don't have a list of filename
2872
     * matches on it. */
2873
    if (!*list)
2874
	refresh_func();
2875
2876

    /* Enable el cursor. */
2877
    curs_set(1);
2878

2879
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2880
}
2881
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2882

2883
2884
/* Only print the last part of a path.  Isn't there a shell command for
 * this? */
2885
2886
const char *tail(const char *foo)
{
2887
    const char *tmp = strrchr(foo, '/');
2888

2889
2890
    if (tmp == NULL)
	tmp = foo;
2891
    else
2892
2893
2894
2895
2896
	tmp++;

    return tmp;
}

2897
#if !defined(NANO_TINY) && !defined(DISABLE_NANORC)
2898
/* Return the constructed dorfile path, or NULL if we can't find the home
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2899
2900
 * directory.  The string is dynamically allocated, and should be
 * freed. */
2901
char *construct_filename(const char *str)
2902
{
2903
    char *newstr = NULL;
2904

2905
2906
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2907

2908
2909
2910
	newstr = charalloc(homelen + strlen(str) + 1);
	strcpy(newstr, homedir);
	strcpy(newstr + homelen, str);
Chris Allegretta's avatar
Chris Allegretta committed
2911
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2912

2913
2914
2915
2916
2917
    return newstr;
}

char *histfilename(void)
{
2918
    return construct_filename("/.nano/search_history");
2919
2920
}

2921
2922
/* Construct the legacy history filename.
 * (Deprecate in 2.5, delete later.) */
2923
2924
2925
2926
2927
2928
2929
char *legacyhistfilename(void)
{
    return construct_filename("/.nano_history");
}

char *poshistfilename(void)
{
2930
    return construct_filename("/.nano/filepos_history");
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
}

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

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

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

2946
2947
2948
/* 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. */
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
int check_dotnano(void)
{
    struct stat dirstat;
    char *nanodir = construct_filename("/.nano");

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

2967
/* Load histories from ~/.nano_history. */
2968
2969
2970
void load_history(void)
{
    char *nanohist = histfilename();
2971
2972
2973
2974
    char *legacyhist = legacyhistfilename();
    struct stat hstat;


2975
    if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
2976
	if (rename(legacyhist, nanohist) == -1)
2977
2978
2979
2980
2981
2982
2983
	    history_error(N_("Detected a legacy nano history file (%s) which I tried to move\nto the preferred location (%s) but encountered an error: %s"),
		legacyhist, nanohist, strerror(errno));
	else
	    history_error(N_("Detected a legacy nano history file (%s) which I moved\nto the preferred location (%s)\n(see the nano FAQ about this change)"),
		legacyhist, nanohist);
    }

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

2988
	if (hist == NULL) {
2989
	    if (errno != ENOENT) {
2990
2991
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2992
		history_error(N_("Error reading %s: %s"), nanohist,
2993
			strerror(errno));
2994
	    }
2995
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2996
2997
2998
	    /* 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. */
2999
	    filestruct **history = &search_history;
3000
	    char *line = NULL;
3001
	    size_t buf_len = 0;
3002
3003
	    ssize_t read;

3004
	    while ((read = getline(&line, &buf_len, hist)) >= 0) {
3005
3006
3007
3008
3009
3010
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
3011
3012
		    update_history(history, line);
		} else
3013
3014
		    history = &replace_history;
	    }
3015

3016
	    fclose(hist);
3017
	    free(line);
3018
3019
	    if (search_history->prev != NULL)
		last_search = mallocstrcpy(NULL, search_history->prev->data);
3020
	}
3021
	free(nanohist);
3022
	free(legacyhist);
3023
3024
3025
    }
}

3026
3027
3028
/* 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. */
3029
bool writehist(FILE *hist, filestruct *h)
3030
{
3031
    filestruct *p;
3032

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

3038
	sunder(p->data);
3039

3040
	if (fwrite(p->data, sizeof(char), p_len, hist) < p_len ||
3041
3042
		putc('\n', hist) == EOF)
	    return FALSE;
3043
    }
3044

3045
    return TRUE;
3046
3047
}

3048
/* Save histories to ~/.nano/search_history. */
3049
3050
void save_history(void)
{
3051
    char *nanohist;
3052

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3053
    /* Don't save unchanged or empty histories. */
3054
3055
    if (!history_has_changed() || (searchbot->lineno == 1 &&
	replacebot->lineno == 1))
3056
3057
	return;

3058
3059
3060
3061
    nanohist = histfilename();

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

3063
	if (hist == NULL)
3064
	    history_error(N_("Error writing %s: %s"), nanohist,
3065
		strerror(errno));
3066
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3067
3068
	    /* Make sure no one else can read from or write to the
	     * history file. */
3069
	    chmod(nanohist, S_IRUSR | S_IWUSR);
3070

3071
3072
	    if (!writehist(hist, searchage) || !writehist(hist,
		replaceage))
3073
		history_error(N_("Error writing %s: %s"), nanohist,
3074
			strerror(errno));
3075

3076
3077
	    fclose(hist);
	}
3078

3079
3080
3081
	free(nanohist);
    }
}
3082

3083
/* Analogs for the POS history. */
3084
3085
3086
void save_poshistory(void)
{
    char *poshist;
3087
3088
    char *statusstr = NULL;
    poshiststruct *posptr;
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102

    poshist = poshistfilename();

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

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

3103
3104
            for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
		statusstr = charalloc(strlen(posptr->filename) + 2 * sizeof(ssize_t) + 4);
3105
3106
		sprintf(statusstr, "%s %ld %ld\n", posptr->filename, (long)posptr->lineno,
			(long)posptr->xno);
3107
3108
3109
		if (fwrite(statusstr, sizeof(char), strlen(statusstr), hist) < strlen(statusstr))
		    history_error(N_("Error writing %s: %s"), poshist,
			strerror(errno));
3110
		free(statusstr);
3111
3112
3113
3114
3115
3116
3117
	    }
	    fclose(hist);
	}
	free(poshist);
    }
}

3118
3119
/* Update the POS history, given a filename line and column.  If no
 * entry is found, add a new entry on the end. */
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
   poshiststruct *posptr, *posprev = NULL;
   char *fullpath = get_full_path(filename);

    if (fullpath == NULL)
        return;

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

    /* Didn't find it, make a new node yo! */
3138
    posptr = (poshiststruct *)nmalloc(sizeof(poshiststruct));
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
    posptr->filename = mallocstrcpy(NULL, fullpath);
    posptr->lineno = lineno;
    posptr->xno = xpos;
    posptr->next = NULL;

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

    free(fullpath);
}


3153
3154
3155
/* Check the POS history to see if file matches an existing entry.  If
 * so, return 1 and set line and column to the right values.  Otherwise,
 * return 0. */
3156
3157
3158
3159
3160
3161
int check_poshistory(const char *file, ssize_t *line, ssize_t *column)
{
    poshiststruct *posptr;
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
3162
	return 0;
3163
3164
3165
3166
3167

    for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
	if (!strcmp(posptr->filename, fullpath)) {
	    *line = posptr->lineno;
	    *column = posptr->xno;
3168
	    free(fullpath);
3169
3170
3171
	    return 1;
	}
    }
3172
    free(fullpath);
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
    return 0;
}

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

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

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

3198
	    /* See if we can find the file we're currently editing. */
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
	    while ((read = getline(&line, &buf_len, hist)) >= 0) {
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
		}
		lineptr = parse_next_word(line);
		xptr = parse_next_word(lineptr);
		lineno = atoi(lineptr);
		xno = atoi(xptr);
		if (poshistory == NULL) {
3212
		    poshistory = (poshiststruct *)nmalloc(sizeof(poshiststruct));
3213
3214
3215
3216
3217
		    poshistory->filename = mallocstrcpy(NULL, line);
		    poshistory->lineno = lineno;
		    poshistory->xno = xno;
		    poshistory->next = NULL;
		} else {
3218
		    for (posptr = poshistory; posptr->next != NULL; posptr = posptr->next)
3219
			;
3220
		    posptr->next = (poshiststruct *)nmalloc(sizeof(poshiststruct));
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
		    posptr->next->filename = mallocstrcpy(NULL, line);
		    posptr->next->lineno = lineno;
		    posptr->next->xno = xno;
		    posptr->next->next = NULL;
		}

	    }

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

3236
#endif /* !NANO_TINY && !DISABLE_NANORC */