files.c 92.3 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   files.c                                                              *
 *                                                                        *
5
 *   Copyright (C) 1999, 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
    char *lockfiledir = NULL;
251
252
253
254
255
256
257
258
    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);
259
#endif
260
261
262
    if (stat(lockfilename, &fileinfo) != -1) {
        ssize_t readtot = 0;
        ssize_t readamt = 0;
263
264
        char *lockbuf = charalloc(8192);
        char *promptstr = charalloc(128);
265
266
        int ans;
        if ((lockfd = open(lockfilename, O_RDONLY)) < 0) {
267
            statusbar(_("Error opening lock file %s: %s"),
268
269
270
271
272
273
274
275
276
                      lockfilename, strerror(errno));
            return -1;
        }
        do {
            readamt = read(lockfd, &lockbuf[readtot], BUFSIZ);
            readtot += readamt;
        } while (readtot < 8192 && readamt > 0);

        if (readtot < 48) {
277
            statusbar(_("Error reading lock file %s: Not enough data read"),
278
279
280
281
282
283
284
285
286
287
288
289
                      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);
290
#endif
291
292
293
294
295
296
297
        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;
        }
298
    } else {
299
300
301
	lockfiledir = mallocstrcpy(NULL, lockfilename);
	lockfiledir = dirname(lockfiledir);
	if (stat(lockfiledir, &fileinfo) == -1) {
302
	    statusbar(_("Error writing lock file: Directory \'%s\' doesn't exist"),
303
		lockfiledir);
304
	    return 0;
305
	}
306
307
    }

308

309
310
    return write_lockfile(lockfilename, filename, FALSE);
}
311
#endif /* !NANO_TINY */
312

313
314
/* 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. */
315
void open_buffer(const char *filename, bool undoable)
316
{
317
    bool new_buffer = (openfile == NULL
318
#ifndef DISABLE_MULTIBUFFER
319
	 || ISSET(MULTIBUFFER)
320
#endif
321
322
323
324
325
326
	);
	/* 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. */
327

328
329
    assert(filename != NULL);

330
331
332
333
334
335
336
#ifndef DISABLE_OPERATINGDIR
    if (check_operating_dir(filename, FALSE)) {
	statusbar(_("Can't insert file from outside of %s"),
		operating_dir);
	return;
    }
#endif
337

338
339
    /* If we're loading into a new buffer, add a new entry to
     * openfile. */
340
341
    if (new_buffer)
	make_new_buffer();
342

343
344
345
346
    /* 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;
347

348
349
    /* If we have a file, and we're loading into a new buffer, update
     * the filename. */
350
351
    if (rc != -1 && new_buffer)
	openfile->filename = mallocstrcpy(openfile->filename, filename);
352

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
353
354
    /* If we have a non-new file, read it in.  Then, if the buffer has
     * no stat, update the stat, if applicable. */
355
    if (rc > 0) {
356
	read_file(f, rc, filename, undoable, new_buffer);
357
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
358
359
360
361
362
	if (openfile->current_stat == NULL) {
	    openfile->current_stat =
		(struct stat *)nmalloc(sizeof(struct stat));
	    stat(filename, openfile->current_stat);
	}
363
#endif
364
365
    }

366
    /* If we have a file, and we're loading into a new buffer, move back
367
368
     * to the beginning of the first line of the buffer. */
    if (rc != -1 && new_buffer) {
369
	openfile->current = openfile->fileage;
370
371
372
	openfile->current_x = 0;
	openfile->placewewant = 0;
    }
373

374
#ifndef DISABLE_COLOR
375
376
    /* If we're loading into a new buffer, update the colors to account
     * for it, if applicable. */
377
378
379
    if (new_buffer)
	color_update();
#endif
380
}
381

382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
#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. */
405
    if (rc > 0)
406
	read_file(f, rc, filename, FALSE, TRUE);
407
408
409
410
411
412
413
414

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

415
/* Update the screen to account for the current buffer. */
416
void display_buffer(void)
417
{
418
    /* Update the titlebar, since the filename may have changed. */
419
    titlebar(NULL);
420

421
#ifndef DISABLE_COLOR
422
423
424
    /* Make sure we're using the buffer's associated colors, if
     * applicable. */
    color_init();
425
426
427
#endif

    /* Update the edit window. */
428
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
429
430
}

431
#ifndef DISABLE_MULTIBUFFER
432
433
434
/* 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
435
{
436
    assert(openfile != NULL);
437

438
439
    /* If only one file buffer is open, indicate it on the statusbar and
     * get out. */
440
    if (openfile == openfile->next) {
441
442
	statusbar(_("No more open file buffers"));
	return;
Chris Allegretta's avatar
Chris Allegretta committed
443
    }
444

445
446
    /* Switch to the next or previous file buffer, depending on the
     * value of next_buf. */
447
    openfile = next_buf ? openfile->next : openfile->prev;
448
449

#ifdef DEBUG
450
    fprintf(stderr, "filename is %s\n", openfile->filename);
451
452
#endif

453
    /* Update the screen to account for the current buffer. */
454
    display_buffer();
455

456
    /* Indicate the switch on the statusbar. */
457
    statusbar(_("Switched to %s"),
458
459
	((openfile->filename[0] == '\0') ? _("New Buffer") :
	openfile->filename));
460
461

#ifdef DEBUG
462
    dump_filestruct(openfile->current);
463
#endif
464
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
465
466
}

467
/* Switch to the previous entry in the openfile filebuffer. */
468
void switch_to_prev_buffer_void(void)
469
{
470
    switch_to_prevnext_buffer(FALSE);
471
}
472

473
/* Switch to the next entry in the openfile filebuffer. */
474
void switch_to_next_buffer_void(void)
475
{
476
    switch_to_prevnext_buffer(TRUE);
477
}
478

479
/* Delete an entry from the openfile filebuffer, and switch to the one
480
481
482
 * after it.  Return TRUE on success, or FALSE if there are no more open
 * file buffers. */
bool close_buffer(void)
483
{
484
    assert(openfile != NULL);
485

486
    /* If only one file buffer is open, get out. */
487
    if (openfile == openfile->next)
488
	return FALSE;
489

490
#ifndef DISABLE_HISTORIES
491
    update_poshistory(openfile->filename, openfile->current->lineno, xplustabs() + 1);
492
#endif
493

494
    /* Switch to the next file buffer. */
495
    switch_to_next_buffer_void();
496

497
    /* Close the file buffer we had open before. */
498
    unlink_opennode(openfile->prev);
499

500
501
502
    /* If only one buffer is open now, show Exit in the help lines. */
    if (openfile == openfile->next)
	exitfunc->desc = exit_tag;
503

504
    return TRUE;
505
}
506
#endif /* !DISABLE_MULTIBUFFER */
507

508
509
510
511
512
/* 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. */
513
514
515
516
517
518
519
520
521
522
523
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;

524
    assert(filename != NULL);
525
526
527
528
529

    /* 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
530
       permissions, just try the relative one. */
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
    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;
}

547
548
549
550
551
552
/* 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)
553
{
554
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
555

556
557
    /* Convert nulls to newlines.  buf_len is the string's real
     * length. */
558
    unsunder(buf, buf_len);
559

560
    assert(openfile->fileage != NULL && strlen(buf) == buf_len);
561

562
    fileptr->data = mallocstrcpy(NULL, buf);
563

564
#ifndef NANO_TINY
565
566
567
568
    /* 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';
569
#endif
570

571
#ifndef DISABLE_COLOR
572
	fileptr->multidata = NULL;
573
574
#endif

575
    if (*first_line_ins) {
576
577
578
	/* Special case: We're inserting with the cursor on the first
	 * line. */
	fileptr->prev = NULL;
579
	fileptr->next = openfile->fileage;
580
	fileptr->lineno = 1;
581
	if (*first_line_ins) {
582
583
584
585
	    *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. */
586
	    openfile->edittop = fileptr;
587
	} else
588
589
	    openfile->filebot = fileptr;
	openfile->fileage = fileptr;
590
591
592
593
594
595
596
    } else {
	assert(prevnode != NULL);

	fileptr->prev = prevnode;
	fileptr->next = NULL;
	fileptr->lineno = prevnode->lineno + 1;
	prevnode->next = fileptr;
597
598
    }

599
600
    return fileptr;
}
601

602
/* Read an open file into the current buffer.  f should be set to the
603
 * open file, and filename should be set to the name of the file.
604
605
606
 * 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. */
607
void read_file(FILE *f, int fd, const char *filename, bool undoable, bool checkwritable)
608
609
610
611
612
613
614
615
616
617
618
619
620
{
    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. */
621
    filestruct *fileptr = openfile->current;
622
623
624
625
626
627
	/* 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. */
628
629
    bool writable = TRUE;
	/* Is the file writable (if we care) */
630
#ifndef NANO_TINY
631
632
    int format = 0;
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
633
#endif
634

635
636
    assert(openfile->fileage != NULL && openfile->current != NULL);

637
638
    buf = charalloc(bufx);
    buf[0] = '\0';
639

640
641
642
643
644
#ifndef NANO_TINY
    if (undoable)
	add_undo(INSERT);
#endif

645
646
647
648
    if (openfile->current == openfile->fileage)
	first_line_ins = TRUE;
    else
	fileptr = openfile->current->prev;
649

650
    /* Read the entire file into the filestruct. */
651
652
653
654
655
656
    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') {
657
#ifndef NANO_TINY
658
659
660
661
662
663
664
665
666
	    /* 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++;
	    }
667
#endif
668

669
670
	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
671

672
673
674
	    /* Reset the line length in preparation for the next
	     * line. */
	    len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
675

676
677
678
	    num_lines++;
	    buf[0] = '\0';
	    i = 0;
679
#ifndef NANO_TINY
680
681
682
683
684
	/* 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') {
685
686
687
688
689
	    /* 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;
690

691
692
	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
693

694
695
696
697
	    /* 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
698

699
700
701
702
	    num_lines++;
	    buf[0] = input;
	    buf[1] = '\0';
	    i = 1;
703
#endif
704
705
706
707
	} else {
	    /* Calculate the total length of the line.  It might have
	     * nulls in it, so we can't just use strlen() here. */
	    len++;
708

709
710
711
712
713
714
715
716
	    /* 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);
717
	    }
718
719
720
721
722
723
724
725
726
727
728

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

    /* Perhaps this could use some better handling. */
    if (ferror(f))
	nperror(filename);
    fclose(f);
729
    if (fd > 0 && checkwritable) {
730
	close(fd);
731
732
	writable = is_file_writable(filename);
    }
733

734
#ifndef NANO_TINY
735
736
737
738
739
740
741
742
    /* 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';
    }
743
#endif
744

745
746
    /* Did we not get a newline and still have stuff to do? */
    if (len > 0) {
747
#ifndef NANO_TINY
748
749
750
751
752
753
754
	/* 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;
755
756
#endif

757
758
759
760
	/* Read in the last line properly. */
	fileptr = read_line(buf, fileptr, &first_line_ins, len);
	num_lines++;
    }
761

762
    free(buf);
763

764
    /* If we didn't get a file and we don't already have one, open a
765
     * blank buffer. */
766
    if (fileptr == NULL)
767
	open_buffer("", FALSE);
768

769
770
    /* Attach the file we got to the filestruct.  If we got a file of
     * zero bytes, don't do anything. */
771
    if (num_lines > 0) {
772
773
774
775
776
777
778
779
780
781
782
783
	/* 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
784
785
	    /* Tack the text at fileptr onto the beginning of the text
	     * at current. */
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
	    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
801
	    /* Move fileptr back one line and blow away the old fileptr,
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
	     * 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. */
818
	renumber(openfile->current);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
819
    }
820

821
822
    openfile->totsize += get_totsize(openfile->fileage,
	openfile->filebot);
823

824
    /* If the NO_NEWLINES flag isn't set, and text has been added to
825
     * the magicline (i.e. a file that doesn't end in a newline has been
826
827
828
     * 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') {
829
830
831
832
833
834
835
836
837
	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();

838
#ifndef NANO_TINY
839
840
841
    if (undoable)
	update_undo(INSERT);

842
843
844
    if (format == 3) {
	if (writable)
	    statusbar(
845
846
847
		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);
848
849
850
851
852
853
	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) {
854
	openfile->fmt = MAC_FILE;
855
856
	if (writable)
	    statusbar(P_("Read %lu line (Converted from Mac format)",
857
858
		"Read %lu lines (Converted from Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
859
860
861
862
	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);
863
    } else if (format == 1) {
864
	openfile->fmt = DOS_FILE;
865
866
	if (writable)
	    statusbar(P_("Read %lu line (Converted from DOS format)",
867
868
		"Read %lu lines (Converted from DOS format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
869
870
871
872
	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);
873
    } else
874
#endif
875
876
877
878
879
880
	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)",
881
882
		(unsigned long)num_lines), (unsigned long)num_lines);
}
Chris Allegretta's avatar
Chris Allegretta committed
883

884
885
886
887
/* 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".
 *
888
 * Return -2 if we say "New File", -1 if the file isn't opened, and the
889
 * fd opened otherwise.  The file might still have an error while reading
890
 * with a 0 return value.  *f is set to the opened file. */
891
892
int open_file(const char *filename, bool newfie, FILE **f)
{
893
    struct stat fileinfo, fileinfo2;
894
    int fd, quiet = 0;
895
    char *full_filename;
896

897
    assert(filename != NULL && f != NULL);
898

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

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

908
909

#ifndef NANO_TINY
910
911
912
913
914
915
916
    if (ISSET(LOCKING)) {
	int lockstatus = do_lockfile(full_filename);
        if (lockstatus < 0)
	    return -1;
	else if (lockstatus == 0)
	    quiet = 1;
    }
917
918
#endif

919
    if (stat(full_filename, &fileinfo) == -1) {
920
921
	/* Well, maybe we can open the file even if the OS says it's
	 * not there. */
922
        if ((fd = open(filename, O_RDONLY)) != -1) {
923
924
	    if (!quiet)
		statusbar(_("Reading File"));
925
926
927
928
	    free(full_filename);
	    return 0;
	}

929
	if (newfie) {
930
931
	    if (!quiet)
		statusbar(_("New File"));
932
933
934
	    return -2;
	}
	statusbar(_("\"%s\" not found"), filename);
935
	beep();
936
937
	return -1;
    } else if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
938
	S_ISBLK(fileinfo.st_mode)) {
939
940
	/* Don't open directories, character files, or block files.
	 * Sorry, /dev/sndstat! */
941
942
	statusbar(S_ISDIR(fileinfo.st_mode) ?
		_("\"%s\" is a directory") :
943
		_("\"%s\" is a device file"), filename);
944
	beep();
945
	return -1;
946
947
948
    } else if ((fd = open(full_filename, O_RDONLY)) == -1) {
	statusbar(_("Error reading %s: %s"), filename,
		strerror(errno));
949
	beep();
950
	return -1;
951
     } else {
952
	/* The file is A-OK.  Open it. */
953
	*f = fdopen(fd, "rb");
954

955
956
957
	if (*f == NULL) {
	    statusbar(_("Error reading %s: %s"), filename,
		strerror(errno));
958
	    beep();
959
960
961
	    close(fd);
	} else
	    statusbar(_("Reading File"));
962
    }
963

964
965
    free(full_filename);

966
967
968
    return fd;
}

969
970
971
972
973
/* 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)
974
{
975
    static int ulmax_digits = -1;
976
977
978
    unsigned long i = 0;
    char *buf;
    size_t namelen, suffixlen;
979

980
    assert(name != NULL && suffix != NULL);
981

982
983
984
    if (ulmax_digits == -1)
	ulmax_digits = digits(ULONG_MAX);

985
986
    namelen = strlen(name);
    suffixlen = strlen(suffix);
Chris Allegretta's avatar
Chris Allegretta committed
987

988
    buf = charalloc(namelen + suffixlen + ulmax_digits + 2);
989
    sprintf(buf, "%s%s", name, suffix);
990

991
992
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
993

994
995
996
997
	if (stat(buf, &fs) == -1)
	    return buf;
	if (i == ULONG_MAX)
	    break;
998

999
1000
1001
	i++;
	sprintf(buf + namelen + suffixlen, ".%lu", i);
    }
Chris Allegretta's avatar
Chris Allegretta committed
1002

1003
1004
1005
    /* We get here only if there is no possible save file.  Blank out
     * the filename to indicate this. */
    null_at(&buf, 0);
1006

1007
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
1008
1009
}

1010
1011
1012
/* 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. */
1013
void do_insertfile(
1014
#ifndef NANO_TINY
1015
1016
1017
1018
1019
1020
1021
1022
1023
	bool execute
#else
	void
#endif
	)
{
    int i;
    const char *msg;
    char *ans = mallocstrcpy(NULL, "");
1024
	/* The last answer the user typed at the statusbar prompt. */
1025
    filestruct *edittop_save = openfile->edittop;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1026
    size_t current_x_save = openfile->current_x;
1027
    ssize_t current_y_save = openfile->current_y;
1028
    bool edittop_inside = FALSE;
1029
#ifndef NANO_TINY
1030
    bool right_side_up = FALSE, single_line = FALSE;
1031
#endif
1032

1033
1034
    currmenu = MINSERTFILE;

1035
    while (TRUE) {
1036
#ifndef NANO_TINY
1037
	if (execute) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1038
	    msg =
1039
#ifndef DISABLE_MULTIBUFFER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1040
		ISSET(MULTIBUFFER) ?
1041
		_("Command to execute in new buffer [from %s] ") :
1042
#endif
1043
		_("Command to execute [from %s] ");
1044
1045
1046
	} else
#endif /* NANO_TINY */
	{
1047
	    msg =
1048
#ifndef DISABLE_MULTIBUFFER
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1049
		ISSET(MULTIBUFFER) ?
1050
		_("File to insert into new buffer [from %s] ") :
1051
#endif
1052
		_("File to insert [from %s] ");
1053
	}
1054

1055
	i = do_prompt(TRUE,
1056
1057
1058
#ifndef DISABLE_TABCOMP
		TRUE,
#endif
1059
#ifndef NANO_TINY
1060
		execute ? MEXTCMD :
1061
#endif
1062
		MINSERTFILE, ans,
1063
#ifndef DISABLE_HISTORIES
1064
		NULL,
1065
#endif
1066
		edit_refresh, msg,
1067
#ifndef DISABLE_OPERATINGDIR
1068
1069
		operating_dir != NULL && strcmp(operating_dir,
		".") != 0 ? operating_dir :
1070
1071
#endif
		"./");
1072

1073
	/* If we're in multibuffer mode and the filename or command is
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1074
	 * blank, open a new buffer instead of canceling.  If the
1075
1076
	 * 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
1077
	if (i == -1 || ((i == -2 || *answer == '\n')
1078
#ifndef DISABLE_MULTIBUFFER
1079
1080
1081
1082
1083
1084
		&& !ISSET(MULTIBUFFER)
#endif
		)) {
	    statusbar(_("Cancelled"));
	    break;
	} else {
1085
	    size_t pww_save = openfile->placewewant;
1086
	    functionptrtype func = func_from_key(&i);
1087

1088
	    ans = mallocstrcpy(ans, answer);
1089

1090
#ifndef NANO_TINY
1091
#ifndef DISABLE_MULTIBUFFER
1092
	    if (func == new_buffer_void) {
1093
1094
1095
		/* Don't allow toggling if we're in view mode. */
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
1096
1097
		else
		    beep();
1098
		continue;
1099
	    }
1100
#endif
1101
	    if (func == flip_execute_void) {
1102
1103
1104
		execute = !execute;
		continue;
	    }
1105
#endif /* !NANO_TINY */
1106

1107
#ifndef DISABLE_BROWSER
1108
	    if (func == to_files_void) {
1109
		char *tmp = do_browse_from(answer);
1110

1111
1112
		if (tmp == NULL)
		    continue;
1113

1114
		/* We have a file now.  Indicate this. */
1115
1116
		free(answer);
		answer = tmp;
1117

1118
1119
1120
		i = 0;
	    }
#endif
1121

1122
1123
1124
	    /* If we don't have a file yet, go back to the statusbar
	     * prompt. */
	    if (i != 0
1125
#ifndef DISABLE_MULTIBUFFER
1126
1127
1128
1129
		&& (i != -2 || !ISSET(MULTIBUFFER))
#endif
		)
		continue;
1130

1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
#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

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

1163
1164
	    /* Convert newlines to nulls, just before we insert the file
	     * or execute the command. */
1165
	    sunder(answer);
1166
	    align(&answer);
1167

1168
#ifndef NANO_TINY
1169
	    if (execute) {
1170
#ifndef DISABLE_MULTIBUFFER
1171
		if (ISSET(MULTIBUFFER))
1172
		    /* Open a blank buffer. */
1173
		    open_buffer("", FALSE);
1174
1175
1176
#endif

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

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

		/* Save the file specified in answer in the current
		 * buffer. */
1197
		open_buffer(answer, TRUE);
1198
#ifndef NANO_TINY
1199
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1200
#endif
1201

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

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

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

		/* Update the current x-coordinate to account for the
1227
1228
1229
1230
		 * 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. */
1231
		openfile->current_x = strlen(openfile->filebot->data);
1232
1233
1234
1235
		if (openfile->fileage == openfile->filebot) {
#ifndef NANO_TINY
		    if (openfile->mark_set) {
			openfile->mark_begin = openfile->current;
1236
			if (!right_side_up)
1237
1238
1239
1240
			    openfile->mark_begin_x +=
				openfile->current_x;
		    }
#endif
1241
		    openfile->current_x += current_x_save;
1242
		}
1243
#ifndef NANO_TINY
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
		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;
		    }
		}
1254
#endif
1255
1256
1257

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

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

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

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

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

1276
1277
		/* Mark the file as modified. */
		set_modified();
1278

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

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

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

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

    display_main_list();
}

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

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

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

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

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

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

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

1356
    d_there = real_dir_from_tilde(origpath);
1357

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

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

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

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

1377
1378
1379
1380
    /* If we didn't find one, then make sure the answer is in the format
     * "d_here/d_there". */
    if (last_slash == NULL) {
	assert(!path_only);
1381

1382
1383
1384
1385
1386
1387
1388
1389
	d_there_file = d_there;
	d_there = d_here;
    } else {
	/* If path_only is FALSE, then save the filename portion of the
	 * answer (everything after the last slash) in d_there_file. */
	if (!path_only)
	    d_there_file = mallocstrcpy(NULL, last_slash + 1);

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1393
	/* Go to the path specified in d_there. */
1394
1395
1396
	if (chdir(d_there) == -1) {
	    free(d_there);
	    d_there = NULL;
1397
	} else {
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
	    free(d_there);

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

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

		/* If the current directory isn't "/", tack a slash onto
		 * the end of it. */
		if (strcmp(d_there, "/") != 0) {
		    d_there = charealloc(d_there, strlen(d_there) + 2);
		    strcat(d_there, "/");
		}
	    } else
		/* Otherwise, set path_only to TRUE, so that we clean up
		 * correctly, free all allocated memory, and return
		 * NULL. */
		path_only = TRUE;

	    /* Finally, go back to the path specified in d_here,
	     * where we were before.  We don't check for a chdir()
1422
	     * error, since we can do nothing if we get one. */
1423
	    IGNORE_CALL_RESULT(chdir(d_here));
1424

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

1430
1431
1432
1433
1434
1435
1436
    /* At this point, if path_only is FALSE and d_there isn't NULL,
     * d_there contains the path portion of the answer and d_there_file
     * contains the filename portion of the answer.  If this is the
     * case, tack the latter onto the end of the former.  d_there will
     * then contain the complete answer. */
    if (!path_only && d_there != NULL) {
	d_there = charealloc(d_there, strlen(d_there) +
1437
		strlen(d_there_file) + 1);
1438
1439
	strcat(d_there, d_there_file);
    }
1440

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

1445
    return d_there;
1446
}
1447

1448
1449
1450
/* 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. */
1451
char *check_writable_directory(const char *path)
1452
{
1453
1454
    char *full_path = get_full_path(path);

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

1459
1460
1461
    /* 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
1462
	full_path[strlen(full_path) - 1] != '/') {
1463
	free(full_path);
1464
	return NULL;
1465
    }
1466

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

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

1482
1483
    assert(f != NULL);

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

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

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

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

1503
1504
1505
1506
1507
1508
1509
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

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

1515
1516
    umask(original_umask);

1517
    return full_tempdir;
1518
}
1519
1520

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

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

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

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

1552
    char *fullpath;
1553
    bool retval = FALSE;
1554
    const char *whereami1, *whereami2 = NULL;
1555

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

1560
    assert(full_operating_dir != NULL);
1561
1562

    fullpath = get_full_path(currpath);
1563

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

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

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

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

1592
#ifndef NANO_TINY
1593
1594
/* Although this sucks, it sucks less than having a single 'my system is
 * messed up and I'm blanket allowing insecure file writing operations'. */
1595
1596
1597
int prompt_failed_backupwrite(const char *filename)
{
    static int i;
1598
1599
1600
    static char *prevfile = NULL; /* What was the last file we were
                                   * passed so we don't keep asking
                                   * this?  Though maybe we should... */
1601
1602
1603
1604
1605
1606
1607
1608
    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;
}

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

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

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

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

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

1660
1661
1662
    return retval;
}

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

1710
    assert(name != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1711

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

1715
1716
1717
    if (f_open != NULL)
	f = f_open;

1718
1719
    if (!tmp)
	titlebar(NULL);
1720

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1721
    realname = real_dir_from_tilde(name);
1722

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

1732
    anyexists = (lstat(realname, &lst) != -1);
1733

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

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

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

1750
1751
1752
1753
    /* 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. */
1754
1755
    if (openfile->current_stat == NULL && !tmp && realexists) {
	openfile->current_stat = (struct stat *)nmalloc(sizeof(struct stat));
1756
	stat(realname, openfile->current_stat);
1757
    }
1758

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

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

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

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

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

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

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

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

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

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

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

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

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

1887
1888
1889
1890
	if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1
		&& !ISSET(INSECURE_BACKUP)) {
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1891
	    statusbar(_("Error writing backup file %s: %s"), backupname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1892
		strerror(errno));
1893
	    free(backupname);
1894
	    fclose(backup_file);
1895
1896
1897
1898
1899
	    /* 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;
1900
1901
1902
	}

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

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

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

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

1929
1930
	free(backupname);
    }
1931

1932
    skip_backup:
1933
#endif /* !NANO_TINY */
1934

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

1944
1945
    if (f_open == NULL) {
	original_umask = umask(0);
1946

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

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

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

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

1971
	tempname = safe_tempfile(&f);
1972

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

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

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

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

2004
2005
2006
    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
2007
2008
2009
	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);
2010

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

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

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

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

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

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

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

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

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

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2058
	/* If we're on the last line of the file, don't write a newline
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2059
2060
2061
2062
2063
2064
2065
	 * 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 {
2066
#ifndef NANO_TINY
2067
2068
2069
2070
	    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
2071
			strerror(errno));
2072
2073
2074
		    fclose(f);
		    goto cleanup_and_exit;
		}
2075
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2076

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

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

2094
    /* If we're prepending, open the temp file, and append it to f. */
2095
    if (append == PREPEND) {
2096
2097
2098
	int fd_source;
	FILE *f_source = NULL;

2099
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
2100

2101
2102
2103
2104
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
2105
	}
2106

2107
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2108
2109
	    statusbar(_("Error reading %s: %s"), tempname,
		strerror(errno));
2110
	    beep();
2111
	    fclose(f);
2112
	    goto cleanup_and_exit;
2113
2114
	}

2115
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2116
2117
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
2118
	    goto cleanup_and_exit;
2119
	}
2120
2121
2122
2123
2124
    } else if (fclose(f) != 0) {
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
	    goto cleanup_and_exit;
    }
2125

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

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

2145
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2146
2147
2148
2149
	/* 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));
2150
2151
	if (!openfile->mark_set)
	    stat(realname, openfile->current_stat);
2152
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2153

2154
2155
	statusbar(P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten),
2156
		(unsigned long)lineswritten);
2157
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2158
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2159
    }
2160

2161
    retval = TRUE;
2162
2163
2164

  cleanup_and_exit:
    free(realname);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2165
2166
    if (tempname != NULL)
	free(tempname);
2167

2168
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2169
2170
}

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

2185
2186
    assert(openfile->mark_set);

2187
2188
2189
    /* Partition the filestruct so that it contains only the marked
     * text. */
    mark_order((const filestruct **)&top, &top_x,
2190
	(const filestruct **)&bot, &bot_x, NULL);
2191
    filepart = partition_filestruct(top, top_x, bot, bot_x);
2192

2193
2194
2195
2196
2197
2198
    /* 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')))
2199
	new_magicline();
2200

2201
    retval = write_file(name, f_open, tmp, append, TRUE);
2202

2203
2204
2205
    /* If the NO_NEWLINES flag isn't set, and we added a magicline,
     * remove it now. */
    if (!ISSET(NO_NEWLINES) && added_magicline)
2206
2207
2208
2209
	remove_magicline();

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

2212
    if (old_modified)
2213
2214
2215
2216
	set_modified();

    return retval;
}
2217

2218
#endif /* !NANO_TINY */
2219

2220
2221
2222
/* 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
2223
2224
 * TEMP_FILE flag is set and the current file has a name.  Return TRUE
 * on success or FALSE on error. */
2225
bool do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
2226
{
2227
    int i;
2228
    append_type append = OVERWRITE;
2229
    char *ans;
2230
	/* The last answer the user typed at the statusbar prompt. */
2231
#ifndef DISABLE_EXTRA
2232
    static bool did_credits = FALSE;
2233
#endif
2234
    bool retval = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2235

2236
    currmenu = MWRITEFILE;
2237

2238
    if (exiting && openfile->filename[0] != '\0' && ISSET(TEMP_FILE)) {
2239
	retval = write_file(openfile->filename, NULL, FALSE, OVERWRITE,
2240
		FALSE);
2241
2242

	/* Write succeeded. */
2243
	if (retval)
2244
	    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2245
2246
    }

2247
    ans = mallocstrcpy(NULL,
2248
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2249
	(!exiting && openfile->mark_set) ? "" :
2250
#endif
2251
	openfile->filename);
2252
2253
2254

    while (TRUE) {
	const char *msg;
2255
#ifndef NANO_TINY
2256
2257
	const char *formatstr, *backupstr;

2258
	formatstr = (openfile->fmt == DOS_FILE) ?
2259
2260
		_(" [DOS Format]") : (openfile->fmt == MAC_FILE) ?
		_(" [Mac Format]") : "";
2261

2262
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2263

2264
	/* If we're using restricted mode, don't display the "Write
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2265
2266
2267
	 * Selection to File" prompt.  This function is disabled, since
	 * it allows reading from or writing to files not specified on
	 * the command line. */
2268
	if (!ISSET(RESTRICTED) && !exiting && openfile->mark_set)
2269
	    msg = (append == PREPEND) ?
2270
2271
2272
		_("Prepend Selection to File") : (append == APPEND) ?
		_("Append Selection to File") :
		_("Write Selection to File");
2273
	else
2274
#endif /* !NANO_TINY */
2275
2276
2277
	    msg = (append == PREPEND) ? _("File Name to Prepend to") :
		(append == APPEND) ? _("File Name to Append to") :
		_("File Name to Write");
2278

2279
2280
2281
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
2282
	i = do_prompt(!ISSET(RESTRICTED) ||
2283
2284
2285
2286
		openfile->filename[0] == '\0',
#ifndef DISABLE_TABCOMP
		TRUE,
#endif
2287
		MWRITEFILE, ans,
2288
#ifndef DISABLE_HISTORIES
2289
2290
		NULL,
#endif
2291
		edit_refresh, "%s%s%s", msg,
2292
2293
#ifndef NANO_TINY
		formatstr, backupstr
2294
#else
2295
		"", ""
2296
2297
2298
#endif
		);

2299
2300
	/* 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
2301
	if (i < 0 || *answer == '\n') {
2302
	    statusbar(_("Cancelled"));
2303
	    retval = FALSE;
2304
2305
	    break;
	} else {
2306
2307
	    functionptrtype func = func_from_key(&i);

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

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

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

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

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

2350
#ifndef DISABLE_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2351
2352
2353
2354
2355
2356
2357
2358
	    /* If the current file has been modified, we've pressed
	     * Ctrl-X at the edit window to exit, we've pressed "y" at
	     * the "Save modified buffer" prompt to save, we've entered
	     * "zzy" as the filename to save under (hence "xyzzy"), and
	     * this is the first time we've done this, show an Easter
	     * egg.  Display the credits. */
	    if (!did_credits && exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0) {
2359
		do_credits();
2360
		did_credits = TRUE;
2361
		retval = FALSE;
2362
2363
		break;
	    }
2364
#endif
2365

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

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

2376
2377
		full_answer = get_full_path(answer);
		full_filename = get_full_path(openfile->filename);
2378
2379
		name_exists = (stat((full_answer == NULL) ? answer :
			full_answer, &st) != -1);
2380
2381
2382
2383
2384
2385
		if (openfile->filename[0] == '\0')
		    do_warning = name_exists;
		else
		    do_warning = (strcmp((full_answer == NULL) ?
			answer : full_answer, (full_filename == NULL) ?
			openfile->filename : full_filename) != 0);
2386

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

2391
2392
2393
2394
		if (full_filename != NULL)
		    free(full_filename);
		if (full_answer != NULL)
		    free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2395

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

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

2437
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2438

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

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

2455
2456
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2457
    }
2458
2459

    free(ans);
2460

2461
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2462
2463
}

2464
2465
/* Write the current file to disk.  If the mark is on, write the current
 * marked selection to disk. */
2466
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
2467
{
2468
    do_writeout(FALSE);
2469
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
2470
}
Chris Allegretta's avatar
Chris Allegretta committed
2471

2472
2473
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
2474
char *real_dir_from_tilde(const char *buf)
2475
{
2476
    char *retval;
2477

2478
    assert(buf != NULL);
2479

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2480
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2481
	size_t i = 1;
2482
	char *tilde_dir;
2483

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

2488
2489
2490
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
2491
	    tilde_dir = mallocstrcpy(NULL, homedir);
2492
2493
2494
	} else {
	    const struct passwd *userdata;

2495
2496
2497
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

2498
2499
	    do {
		userdata = getpwent();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2500
2501
	    } while (userdata != NULL && strcmp(userdata->pw_name,
		tilde_dir + 1) != 0);
2502
	    endpwent();
2503
	    if (userdata != NULL)
2504
		tilde_dir = mallocstrcpy(tilde_dir, userdata->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2505
	}
2506

2507
2508
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2509

2510
2511
2512
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2513

2514
    return retval;
2515
2516
}

2517
#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
2518
/* Our sort routine for file listings.  Sort alphabetically and
2519
 * case-insensitively, and sort directories before filenames. */
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
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;

2533
2534
2535
    /* 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
2536
     * have to use multibyte strcasecmp() instead. */
2537
    return mbstrcasecmp(a, b);
2538
}
2539
2540
2541
2542
2543

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

2546
2547
2548
2549
    for (; len > 0; len--)
	free(array[len - 1]);
    free(array);
}
2550
2551
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2552
#ifndef DISABLE_TABCOMP
2553
/* Is the given path a directory? */
2554
bool is_dir(const char *buf)
2555
{
2556
    char *dirptr;
2557
    struct stat fileinfo;
2558
2559
2560
    bool retval;

    assert(buf != NULL);
2561

2562
2563
2564
2565
    dirptr = real_dir_from_tilde(buf);

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

2567
    free(dirptr);
2568

2569
    return retval;
2570
}
Chris Allegretta's avatar
Chris Allegretta committed
2571

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2572
2573
2574
/* 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
2575
 * file, with the copyright years updated:
Chris Allegretta's avatar
Chris Allegretta committed
2576
2577
2578
 *
 * Termios command line History and Editting, originally
 * intended for NetBSD sh (ash)
2579
 * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
Chris Allegretta's avatar
Chris Allegretta committed
2580
2581
2582
2583
2584
2585
2586
2587
 *      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.
2588
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
2589

2590
/* We consider the first buf_len characters of buf for ~username tab
2591
2592
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
2593
	size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2594
{
2595
2596
    char **matches = NULL;
    const struct passwd *userdata;
2597

2598
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2599

2600
    *num_matches = 0;
2601

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

2607
2608
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2609
2610
2611
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2612
2613
#endif

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2614
2615
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
2616
2617
2618
	    matches[*num_matches] =
		charalloc(strlen(userdata->pw_name) + 2);
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2619
	    ++(*num_matches);
2620
	}
2621
2622
    }
    endpwent();
2623

2624
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2625
2626
}

2627
/* We consider the first buf_len characters of buf for filename tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2628
 * completion. */
2629
char **cwd_tab_completion(const char *buf, bool allow_files, size_t
2630
	*num_matches, size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2631
{
2632
    char *dirname = mallocstrcpy(NULL, buf), *filename;
2633
2634
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2635
    DIR *dir;
2636
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2637

2638
    assert(dirname != NULL && num_matches != NULL);
2639

2640
    *num_matches = 0;
2641
    null_at(&dirname, buf_len);
2642
2643
2644
2645
2646
2647
2648
2649

    /* 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
2650
	*tmpdirname = '\0';
2651
2652
2653
	tmpdirname = dirname;
	dirname = real_dir_from_tilde(dirname);
	free(tmpdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2654
    } else {
2655
2656
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2657
2658
    }

2659
    assert(dirname[strlen(dirname) - 1] == '/');
2660

Chris Allegretta's avatar
Chris Allegretta committed
2661
    dir = opendir(dirname);
2662

2663
    if (dir == NULL) {
2664
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2665
	beep();
2666
2667
2668
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2669
    }
2670
2671
2672

    filenamelen = strlen(filename);

2673
    while ((nextdir = readdir(dir)) != NULL) {
2674
2675
	bool skip_match = FALSE;

Chris Allegretta's avatar
Chris Allegretta committed
2676
#ifdef DEBUG
2677
	fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2678
#endif
2679
	/* See if this matches. */
2680
	if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
2681
2682
		(*filename == '.' || (strcmp(nextdir->d_name, ".") !=
		0 && strcmp(nextdir->d_name, "..") != 0))) {
2683
2684
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2685

2686
2687
2688
2689
	    char *tmp = charalloc(strlen(dirname) +
		strlen(nextdir->d_name) + 1);
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

2690
2691
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2692
2693
2694
2695
2696
2697
2698
	     * 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. */
2699
	    if (!allow_files && !is_dir(tmp))
2700
2701
2702
		skip_match = TRUE;

	    free(tmp);
2703

2704
	    if (skip_match)
2705
		continue;
2706

2707
2708
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
2709
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2710
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2711
2712
	}
    }
2713

2714
2715
    closedir(dir);
    free(dirname);
2716
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2717

2718
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2719
2720
}

2721
/* Do tab completion.  place refers to how much the statusbar cursor
2722
2723
 * position should be advanced.  refresh_func is the function we will
 * call to refresh the edit window. */
2724
char *input_tab(char *buf, bool allow_files, size_t *place, bool
2725
	*lastwastab, void (*refresh_func)(void), bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2726
{
2727
    size_t num_matches = 0, buf_len;
2728
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2729

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2732
    *list = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2733

2734
2735
    /* 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
2736
    if (*place > 0 && *buf == '~') {
2737
	const char *bob = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2738

2739
2740
2741
2742
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2743

2744
2745
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
2746
2747
	matches = cwd_tab_completion(buf, allow_files, &num_matches,
		*place);
2748

2749
2750
2751
    buf_len = strlen(buf);

    if (num_matches == 0 || *place != buf_len)
2752
2753
2754
2755
	beep();
    else {
	size_t match, common_len = 0;
	char *mzero;
2756
2757
2758
	const char *lastslash = revstrstr(buf, "/", buf + *place);
	size_t lastslash_len = (lastslash == NULL) ? 0 :
		lastslash - buf + 1;
2759
2760
2761
	char *match1_mb = charalloc(mb_cur_max() + 1);
	char *match2_mb = charalloc(mb_cur_max() + 1);
	int match1_mb_len, match2_mb_len;
2762
2763
2764

	while (TRUE) {
	    for (match = 1; match < num_matches; match++) {
2765
2766
		/* Get the number of single-byte characters that all the
		 * matches have in common. */
2767
		match1_mb_len = parse_mbchar(matches[0] + common_len,
2768
			match1_mb, NULL);
2769
		match2_mb_len = parse_mbchar(matches[match] +
2770
			common_len, match2_mb, NULL);
2771
2772
2773
		match1_mb[match1_mb_len] = '\0';
		match2_mb[match2_mb_len] = '\0';
		if (strcmp(match1_mb, match2_mb) != 0)
2774
2775
		    break;
	    }
2776

2777
	    if (match < num_matches || matches[0][common_len] == '\0')
2778
		break;
2779

2780
	    common_len += parse_mbchar(buf + common_len, NULL, NULL);
2781
	}
2782

2783
2784
2785
	free(match1_mb);
	free(match2_mb);

2786
	mzero = charalloc(lastslash_len + common_len + 1);
2787
2788
2789

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

2791
	common_len += lastslash_len;
2792
	mzero[common_len] = '\0';
2793

2794
	assert(common_len >= *place);
2795

2796
	if (num_matches == 1 && is_dir(mzero)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2797
	    mzero[common_len++] = '/';
2798

2799
2800
	    assert(common_len > *place);
	}
2801

2802
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2803
	    beep();
2804

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2805
	/* If there is more of a match to display on the statusbar, show
2806
	 * it.  We reset lastwastab to FALSE: it requires pressing Tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2807
	 * twice in succession with no statusbar changes to see a match
2808
2809
2810
	 * list. */
	if (common_len != *place) {
	    *lastwastab = FALSE;
2811
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2812
2813
	    charmove(buf + common_len, buf + *place, buf_len -
		*place + 1);
2814
	    strncpy(buf, mzero, common_len);
2815
	    *place = common_len;
2816
	} else if (!*lastwastab || num_matches < 2)
2817
2818
	    *lastwastab = TRUE;
	else {
2819
	    int longest_name = 0, ncols, editline = 0;
2820

2821
2822
2823
2824
2825
2826
2827
2828
	    /* 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
2829

2830
2831
		if (common_len > COLS - 1) {
		    longest_name = COLS - 1;
2832
2833
		    break;
		}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2834

2835
2836
		if (common_len > longest_name)
		    longest_name = common_len;
2837
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2838

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

2841
	    /* Each column will be (longest_name + 2) columns wide, i.e.
2842
2843
	     * two spaces between columns, except that there will be
	     * only one space after the last column. */
2844
	    ncols = (COLS + 1) / (longest_name + 2);
2845

2846
2847
2848
2849
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2850

2851
2852
	    /* Disable el cursor. */
	    curs_set(0);
2853

2854
2855
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2856

2857
		wmove(edit, editline, (longest_name + 2) *
2858
			(match % ncols));
2859

2860
		if (match % ncols == 0 &&
2861
			editline == editwinrows - 1 &&
2862
			num_matches - match > ncols) {
2863
2864
2865
		    waddstr(edit, _("(more)"));
		    break;
		}
2866

2867
2868
2869
2870
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2871

2872
		if ((match + 1) % ncols == 0)
2873
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2874
	    }
2875

2876
	    wnoutrefresh(edit);
2877
	    *list = TRUE;
2878
2879
2880
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2881
2882
    }

2883
    free_chararray(matches, num_matches);
2884

2885
    /* Only refresh the edit window if we don't have a list of filename
2886
     * matches on it. */
2887
    if (!*list)
2888
	refresh_func();
2889
2890

    /* Enable el cursor. */
2891
    curs_set(1);
2892

2893
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2894
}
2895
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2896

2897
2898
/* Only print the last part of a path.  Isn't there a shell command for
 * this? */
2899
2900
const char *tail(const char *foo)
{
2901
    const char *tmp = strrchr(foo, '/');
2902

2903
2904
    if (tmp == NULL)
	tmp = foo;
2905
    else
2906
2907
2908
2909
2910
	tmp++;

    return tmp;
}

2911
2912
#ifndef DISABLE_HISTORIES
/* Return the constructed dirfile path, or NULL if we can't find the home
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2913
2914
 * directory.  The string is dynamically allocated, and should be
 * freed. */
2915
char *construct_filename(const char *str)
2916
{
2917
    char *newstr = NULL;
2918

2919
2920
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2921

2922
2923
2924
	newstr = charalloc(homelen + strlen(str) + 1);
	strcpy(newstr, homedir);
	strcpy(newstr + homelen, str);
Chris Allegretta's avatar
Chris Allegretta committed
2925
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2926

2927
2928
2929
2930
2931
    return newstr;
}

char *histfilename(void)
{
2932
    return construct_filename("/.nano/search_history");
2933
2934
}

2935
2936
/* Construct the legacy history filename.
 * (Deprecate in 2.5, delete later.) */
2937
2938
2939
2940
2941
2942
2943
char *legacyhistfilename(void)
{
    return construct_filename("/.nano_history");
}

char *poshistfilename(void)
{
2944
    return construct_filename("/.nano/filepos_history");
2945
2946
2947
2948
}

void history_error(const char *msg, ...)
{
2949
    va_list ap;
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959

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

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

2960
2961
2962
/* 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. */
2963
2964
2965
2966
2967
2968
2969
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) {
2970
2971
2972
	    history_error(N_("Unable to create directory %s: %s\n"
			     "It is required for saving/loading search history or cursor positions.\n"),
				nanodir, strerror(errno));
2973
2974
2975
	    return 0;
	}
    } else if (!S_ISDIR(dirstat.st_mode)) {
2976
2977
2978
	history_error(N_("Path %s is not a directory and needs to be.\n"
			 "Nano will be unable to load or save search history or cursor positions.\n"),
				nanodir);
2979
2980
2981
	return 0;
    }
    return 1;
2982
2983
}

2984
/* Load the search and replace histories from ~/.nano/search_history. */
2985
2986
2987
void load_history(void)
{
    char *nanohist = histfilename();
2988
2989
2990
    char *legacyhist = legacyhistfilename();
    struct stat hstat;

2991
    if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
2992
	if (rename(legacyhist, nanohist) == -1)
2993
2994
2995
	    history_error(N_("Detected a legacy nano history file (%s) which I tried to move\n"
			     "to the preferred location (%s) but encountered an error: %s"),
				legacyhist, nanohist, strerror(errno));
2996
	else
2997
2998
2999
	    history_error(N_("Detected a legacy nano history file (%s) which I moved\n"
			     "to the preferred location (%s)\n(see the nano FAQ about this change)"),
				legacyhist, nanohist);
3000
3001
    }

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

3006
	if (hist == NULL) {
3007
	    if (errno != ENOENT) {
3008
3009
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
3010
		history_error(N_("Error reading %s: %s"), nanohist,
3011
			strerror(errno));
3012
	    }
3013
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3014
3015
3016
	    /* 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. */
3017
	    filestruct **history = &search_history;
3018
	    char *line = NULL;
3019
	    size_t buf_len = 0;
3020
3021
	    ssize_t read;

3022
	    while ((read = getline(&line, &buf_len, hist)) >= 0) {
3023
3024
3025
3026
3027
3028
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
3029
3030
		    update_history(history, line);
		} else
3031
3032
		    history = &replace_history;
	    }
3033

3034
	    fclose(hist);
3035
	    free(line);
3036
3037
	    if (search_history->prev != NULL)
		last_search = mallocstrcpy(NULL, search_history->prev->data);
3038
	}
3039
	free(nanohist);
3040
	free(legacyhist);
3041
3042
3043
    }
}

3044
3045
3046
/* 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. */
3047
bool writehist(FILE *hist, filestruct *h)
3048
{
3049
    filestruct *p;
3050

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

3056
	sunder(p->data);
3057

3058
	if (fwrite(p->data, sizeof(char), p_len, hist) < p_len ||
3059
3060
		putc('\n', hist) == EOF)
	    return FALSE;
3061
    }
3062

3063
    return TRUE;
3064
3065
}

3066
/* Save the search and replace histories to ~/.nano/search_history. */
3067
3068
void save_history(void)
{
3069
    char *nanohist;
3070

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3071
    /* Don't save unchanged or empty histories. */
3072
3073
    if (!history_has_changed() || (searchbot->lineno == 1 &&
	replacebot->lineno == 1))
3074
3075
	return;

3076
3077
3078
3079
    nanohist = histfilename();

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

3081
	if (hist == NULL)
3082
	    history_error(N_("Error writing %s: %s"), nanohist,
3083
		strerror(errno));
3084
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3085
3086
	    /* Make sure no one else can read from or write to the
	     * history file. */
3087
	    chmod(nanohist, S_IRUSR | S_IWUSR);
3088

3089
3090
	    if (!writehist(hist, searchage) || !writehist(hist,
		replaceage))
3091
		history_error(N_("Error writing %s: %s"), nanohist,
3092
			strerror(errno));
3093

3094
3095
	    fclose(hist);
	}
3096

3097
3098
3099
	free(nanohist);
    }
}
3100

3101
/* Save the recorded last file positions to ~/.nano/filepos_history. */
3102
3103
3104
void save_poshistory(void)
{
    char *poshist;
3105
3106
    char *statusstr = NULL;
    poshiststruct *posptr;
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120

    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);

3121
	    for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
3122
		statusstr = charalloc(strlen(posptr->filename) + 2 * sizeof(ssize_t) + 4);
3123
3124
		sprintf(statusstr, "%s %ld %ld\n", posptr->filename, (long)posptr->lineno,
			(long)posptr->xno);
3125
3126
3127
		if (fwrite(statusstr, sizeof(char), strlen(statusstr), hist) < strlen(statusstr))
		    history_error(N_("Error writing %s: %s"), poshist,
			strerror(errno));
3128
		free(statusstr);
3129
3130
3131
3132
3133
3134
3135
	    }
	    fclose(hist);
	}
	free(poshist);
    }
}

3136
3137
/* Update the recorded last file positions, given a filename, a line
 * and a column.  If no entry is found, add a new one at the end. */
3138
3139
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
3140
3141
    poshiststruct *posptr, *posprev = NULL;
    char *fullpath = get_full_path(filename);
3142
3143

    if (fullpath == NULL)
3144
	return;
3145
3146

    for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
3147
	if (!strcmp(posptr->filename, fullpath)) {
3148
3149
	    posptr->lineno = lineno;
	    posptr->xno = xpos;
3150
3151
	    return;
	}
3152
3153
3154
3155
	posprev = posptr;
    }

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

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

    free(fullpath);
}

3170
3171
3172
/* Check the recorded last file positions to see if the given file
 * matches an existing entry.  If so, return 1 and set line and column
 * to the retrieved values.  Otherwise, return 0. */
3173
3174
3175
3176
3177
3178
int check_poshistory(const char *file, ssize_t *line, ssize_t *column)
{
    poshiststruct *posptr;
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
3179
	return 0;
3180
3181
3182
3183
3184

    for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
	if (!strcmp(posptr->filename, fullpath)) {
	    *line = posptr->lineno;
	    *column = posptr->xno;
3185
	    free(fullpath);
3186
3187
3188
	    return 1;
	}
    }
3189
    free(fullpath);
3190
3191
3192
    return 0;
}

3193
/* Load the recorded file positions from ~/.nano/filepos_history. */
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
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. */
3205
		UNSET(POS_HISTORY);
3206
3207
3208
3209
3210
3211
3212
3213
3214
		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;

3215
3216
	    /* Read and parse each line, and put the data into the
	     * positions history structure. */
3217
3218
3219
3220
3221
	    while ((read = getline(&line, &buf_len, hist)) >= 0) {
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
3222
		if (read > 0)
3223
3224
3225
3226
3227
3228
		    unsunder(line, read);
		lineptr = parse_next_word(line);
		xptr = parse_next_word(lineptr);
		lineno = atoi(lineptr);
		xno = atoi(xptr);
		if (poshistory == NULL) {
3229
		    poshistory = (poshiststruct *)nmalloc(sizeof(poshiststruct));
3230
3231
3232
3233
3234
		    poshistory->filename = mallocstrcpy(NULL, line);
		    poshistory->lineno = lineno;
		    poshistory->xno = xno;
		    poshistory->next = NULL;
		} else {
3235
		    for (posptr = poshistory; posptr->next != NULL; posptr = posptr->next)
3236
			;
3237
		    posptr->next = (poshiststruct *)nmalloc(sizeof(poshiststruct));
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
		    posptr->next->filename = mallocstrcpy(NULL, line);
		    posptr->next->lineno = lineno;
		    posptr->next->xno = xno;
		    posptr->next->next = NULL;
		}
	    }

	    fclose(hist);
	    free(line);
	}
	free(nanohist);
    }
}
3251
#endif /* !DISABLE_HISTORIES */