files.c 91.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
6
 *   Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,  *
 *   2008, 2009 Free Software Foundation, Inc.                            *
Chris Allegretta's avatar
Chris Allegretta committed
7
8
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
9
 *   the Free Software Foundation; either version 3, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
10
11
 *   any later version.                                                   *
 *                                                                        *
12
13
14
15
 *   This program is distributed in the hope that it will be useful, but  *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    *
 *   General Public License for more details.                             *
Chris Allegretta's avatar
Chris Allegretta committed
16
17
18
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
19
20
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
 *   02110-1301, USA.                                                     *
Chris Allegretta's avatar
Chris Allegretta committed
21
22
23
 *                                                                        *
 **************************************************************************/

24
#include "proto.h"
25

Chris Allegretta's avatar
Chris Allegretta committed
26
#include <stdio.h>
27
#include <string.h>
Chris Allegretta's avatar
Chris Allegretta committed
28
#include <unistd.h>
29
#include <utime.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
31
#include <fcntl.h>
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
32
#include <ctype.h>
33
#include <pwd.h>
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;
Chris Allegretta's avatar
Chris Allegretta committed
50
    }
51

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

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

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

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

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

69
    openfile->modified = FALSE;
70
#ifndef NANO_TINY
71
    openfile->mark_set = FALSE;
72

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

76
    openfile->fmt = NIX_FILE;
77

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

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

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

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

101
102
103
104
#ifdef ENABLE_COLOR
    openfile->fileage->multidata = NULL;
#endif

105
    openfile->totsize = 0;
106
107
}

108
109
110
111
112
113
114
115
116
117
118

#ifndef NANO_TINY
/* Actyally write the lock file.  This function will
   ALWAYS annihilate any previous version of the file.
   We'll borrow INSECURE_BACKUP here to decide about lock file
   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

119
   Returns: 1 on success, 0 on failure (but continue loading), -1 on failure and abort
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
 */
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;

    /* Run things which might fail first before we try and blow away
       the old state */
    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) {
       statusbar(_("Couldn't determine hosttname for lock file: %s"), strerror(errno));
       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);
157
158
159
160
161
162
163

    /* 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 */
    if (fd < 0 && errno == EACCES)
        return 1;

164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
    /* Now we've got a safe file stream.  If the previous open()
    call failed, this will return NULL. */
    filestream = fdopen(fd, "wb");

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


    /* 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
185
       byte 1007     - 0x55 if file is modified
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201

       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
     */
    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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242

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

#ifdef DEBUG
    fprintf(stderr, "In write_lockfile(), write successful (wrote %d bytes)\n", wroteamt);
#endif /* DEBUG */

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

    openfile->lock_filename = lockfilename;

    return 1;
}


/* Less exciting, delete the lock file.
   Return -1 if successful and complain on the statusbar, 1 otherwite
 */
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;
}


/* Deal with lockfiles.  Return -1 on refusing to override
243
244
245
   the lock file, and 1 on successfully created the lockfile, 0 means
   we were not successful on creating the lockfile but we should
   continue to load the file and complain to the user.
246
247
248
249
250
251
252
 */
int do_lockfile(const char *filename)
{
    char *lockdir = dirname((char *) mallocstrcpy(NULL, filename));
    char *lockbase = basename((char *) mallocstrcpy(NULL, filename));
    ssize_t lockfilesize = (sizeof (char *) * (strlen(filename)
                   + strlen(locking_prefix) + strlen(locking_suffix) + 3));
253
    char *lockfilename = (char *) nmalloc(lockfilesize);
254
255
256
257
258
259
260
261
262
263
264
265
266
    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);
#endif /* DEBUG */
    if (stat(lockfilename, &fileinfo) != -1) {
        ssize_t readtot = 0;
        ssize_t readamt = 0;
267
268
        char *lockbuf = (char *) nmalloc(8192);
        char *promptstr = (char *) nmalloc(128);
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
        int ans;
        if ((lockfd = open(lockfilename, O_RDONLY)) < 0) {
            statusbar(_("Error opening lockfile %s: %s"),
                      lockfilename, strerror(errno));
            return -1;
        }
        do {
            readamt = read(lockfd, &lockbuf[readtot], BUFSIZ);
            readtot += readamt;
        } while (readtot < 8192 && readamt > 0);

        if (readtot < 48) {
            statusbar(_("Error reading lockfile %s: Not enough data read"),
                      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);
#endif /* DEBUG */
        sprintf(promptstr, "File being edited (by %s, PID %d, user %s), continue?",
                              lockprog, lockpid, lockuser);
        ans = do_yesno_prompt(FALSE, promptstr);
        if (ans < 1) {
            blank_statusbar();
            return -1;
        }
    }

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


309
310
/* 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. */
311
void open_buffer(const char *filename, bool undoable)
312
{
313
314
315
    bool new_buffer = (openfile == NULL
#ifdef ENABLE_MULTIBUFFER
	 || ISSET(MULTIBUFFER)
316
#endif
317
318
319
320
321
322
	);
	/* 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. */
323

324
325
    assert(filename != NULL);

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

334
335
    /* If we're loading into a new buffer, add a new entry to
     * openfile. */
336
337
    if (new_buffer)
	make_new_buffer();
338

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

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

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

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

#ifdef ENABLE_COLOR
371
372
    /* If we're loading into a new buffer, update the colors to account
     * for it, if applicable. */
373
374
375
    if (new_buffer)
	color_update();
#endif
376
}
377

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

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

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

#ifdef ENABLE_COLOR
418
419
420
    /* Make sure we're using the buffer's associated colors, if
     * applicable. */
    color_init();
421
422
423
#endif

    /* Update the edit window. */
424
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
425
426
}

427
428
429
430
#ifdef ENABLE_MULTIBUFFER
/* Switch to the next file buffer if next_buf is TRUE.  Otherwise,
 * switch to the previous file buffer. */
void switch_to_prevnext_buffer(bool next_buf)
Chris Allegretta's avatar
Chris Allegretta committed
431
{
432
    assert(openfile != NULL);
433

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

441
442
    /* Switch to the next or previous file buffer, depending on the
     * value of next_buf. */
443
    openfile = next_buf ? openfile->next : openfile->prev;
444
445

#ifdef DEBUG
446
    fprintf(stderr, "filename is %s\n", openfile->filename);
447
448
#endif

449
    /* Update the screen to account for the current buffer. */
450
    display_buffer();
451

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

#ifdef DEBUG
458
    dump_filestruct(openfile->current);
459
#endif
Chris Allegretta's avatar
Chris Allegretta committed
460
461
}

462
/* Switch to the previous entry in the openfile filebuffer. */
463
void switch_to_prev_buffer_void(void)
464
{
465
    switch_to_prevnext_buffer(FALSE);
466
}
467

468
/* Switch to the next entry in the openfile filebuffer. */
469
void switch_to_next_buffer_void(void)
470
{
471
    switch_to_prevnext_buffer(TRUE);
472
}
473

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

481
    /* If only one file buffer is open, get out. */
482
    if (openfile == openfile->next)
483
	return FALSE;
484

485
486
487
488
#ifndef NANO_TINY
        update_poshistory(openfile->filename, openfile->current->lineno, xplustabs()+1);
#endif /* NANO_TINY */

489
    /* Switch to the next file buffer. */
490
    switch_to_next_buffer_void();
491

492
    /* Close the file buffer we had open before. */
493
    unlink_opennode(openfile->prev);
494

495
    display_main_list();
496

497
    return TRUE;
498
}
499
#endif /* ENABLE_MULTIBUFFER */
500

501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
/* A bit of a copy and paste from open_file(), is_file_writable()
 * just checks whether the file is appendable as a quick
 * permissions check, and we tend to err on the side of permissiveness
 * (reporting TRUE when it might be wrong) to not fluster users
 * editing on odd filesystems by printing incorrect warnings.
 */
int is_file_writable(const char *filename)
{
    struct stat fileinfo, fileinfo2;
    int fd;
    FILE *f;
    char *full_filename;
    bool ans = TRUE;


    if (ISSET(VIEW_MODE))
	return TRUE;

519
    assert(filename != NULL);
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541

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

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

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

    free(full_filename);
    return ans;
}

542
543
544
545
546
547
/* 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)
548
{
549
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
550

551
552
    /* Convert nulls to newlines.  buf_len is the string's real
     * length. */
553
    unsunder(buf, buf_len);
554

555
    assert(openfile->fileage != NULL && strlen(buf) == buf_len);
556

557
    fileptr->data = mallocstrcpy(NULL, buf);
558

559
#ifndef NANO_TINY
560
561
562
563
    /* 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';
564
#endif
565

566
#ifdef ENABLE_COLOR
567
	fileptr->multidata = NULL;
568
569
#endif

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

	fileptr->prev = prevnode;
	fileptr->next = NULL;
	fileptr->lineno = prevnode->lineno + 1;
	prevnode->next = fileptr;
592
593
    }

594
595
    return fileptr;
}
596

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

630
631
    assert(openfile->fileage != NULL && openfile->current != NULL);

632
633
    buf = charalloc(bufx);
    buf[0] = '\0';
634

635
636
637
638
639
#ifndef NANO_TINY
    if (undoable)
	add_undo(INSERT);
#endif

640
641
642
643
    if (openfile->current == openfile->fileage)
	first_line_ins = TRUE;
    else
	fileptr = openfile->current->prev;
644

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

664
665
	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
666

667
668
669
	    /* Reset the line length in preparation for the next
	     * line. */
	    len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
670

671
672
673
	    num_lines++;
	    buf[0] = '\0';
	    i = 0;
674
#ifndef NANO_TINY
675
676
677
678
679
	/* 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') {
680
681
682
683
684
	    /* 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;
685

686
687
	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
688

689
690
691
692
	    /* 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
693

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

704
705
706
707
708
709
710
711
	    /* 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);
712
	    }
713
714
715
716
717
718
719
720
721
722
723

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

    /* Perhaps this could use some better handling. */
    if (ferror(f))
	nperror(filename);
    fclose(f);
724
    if (fd > 0 && checkwritable) {
725
	close(fd);
726
727
	writable = is_file_writable(filename);
    }
728

729
#ifndef NANO_TINY
730
731
732
733
734
735
736
737
    /* 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';
    }
738
#endif
739

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

752
753
754
755
	/* Read in the last line properly. */
	fileptr = read_line(buf, fileptr, &first_line_ins, len);
	num_lines++;
    }
756

757
    free(buf);
758

759
    /* If we didn't get a file and we don't already have one, open a
760
     * blank buffer. */
761
    if (fileptr == NULL)
762
	open_buffer("", FALSE);
763

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

816
817
    openfile->totsize += get_totsize(openfile->fileage,
	openfile->filebot);
818

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

833
#ifndef NANO_TINY
834
835
836
    if (undoable)
	update_undo(INSERT);

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

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

892
    assert(filename != NULL && f != NULL);
893

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

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

903
904
905
906
907
908
909

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

910
    if (stat(full_filename, &fileinfo) == -1) {
911
912
913
914
915
916
917
918
	/* Well, maybe we can open the file even if the OS
	   says its not there */
        if ((fd = open(filename, O_RDONLY)) != -1) {
	    statusbar(_("Reading File"));
	    free(full_filename);
	    return 0;
	}

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

944
945
946
	if (*f == NULL) {
	    statusbar(_("Error reading %s: %s"), filename,
		strerror(errno));
947
	    beep();
948
949
950
	    close(fd);
	} else
	    statusbar(_("Reading File"));
951
    }
952

953
954
    free(full_filename);

955
956
957
    return fd;
}

958
959
960
961
962
/* 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)
963
{
964
    static int ulmax_digits = -1;
965
966
967
    unsigned long i = 0;
    char *buf;
    size_t namelen, suffixlen;
968

969
    assert(name != NULL && suffix != NULL);
970

971
972
973
    if (ulmax_digits == -1)
	ulmax_digits = digits(ULONG_MAX);

974
975
    namelen = strlen(name);
    suffixlen = strlen(suffix);
Chris Allegretta's avatar
Chris Allegretta committed
976

977
    buf = charalloc(namelen + suffixlen + ulmax_digits + 2);
978
    sprintf(buf, "%s%s", name, suffix);
979

980
981
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
982

983
984
985
986
	if (stat(buf, &fs) == -1)
	    return buf;
	if (i == ULONG_MAX)
	    break;
987

988
989
990
	i++;
	sprintf(buf + namelen + suffixlen, ".%lu", i);
    }
Chris Allegretta's avatar
Chris Allegretta committed
991

992
993
994
    /* We get here only if there is no possible save file.  Blank out
     * the filename to indicate this. */
    null_at(&buf, 0);
995

996
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
997
998
}

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

1023
1024
    currmenu = MINSERTFILE;

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

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

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

1079
	    ans = mallocstrcpy(ans, answer);
1080

1081
1082
	    s = get_shortcut(currmenu, &i, &meta_key, &func_key);

1083
#ifndef NANO_TINY
1084
#ifdef ENABLE_MULTIBUFFER
1085

1086
	    if (s && s->scfunc == new_buffer_void) {
1087
1088
1089
1090
1091
		/* Don't allow toggling if we're in view mode. */
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
		continue;
	    } else
1092
#endif
1093
	    if (s && s->scfunc == ext_cmd_void) {
1094
1095
1096
1097
1098
1099
		execute = !execute;
		continue;
	    }
#ifndef DISABLE_BROWSER
	    else
#endif
1100
#endif /* !NANO_TINY */
1101

1102
#ifndef DISABLE_BROWSER
1103
	    if (s && s->scfunc == to_files_void) {
1104
		char *tmp = do_browse_from(answer);
1105

1106
1107
		if (tmp == NULL)
		    continue;
1108

1109
		/* We have a file now.  Indicate this. */
1110
1111
		free(answer);
		answer = tmp;
1112

1113
1114
1115
		i = 0;
	    }
#endif
1116

1117
1118
1119
1120
1121
1122
1123
1124
	    /* If we don't have a file yet, go back to the statusbar
	     * prompt. */
	    if (i != 0
#ifdef ENABLE_MULTIBUFFER
		&& (i != -2 || !ISSET(MULTIBUFFER))
#endif
		)
		continue;
1125

1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
#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

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

1158
1159
	    /* Convert newlines to nulls, just before we insert the file
	     * or execute the command. */
1160
	    sunder(answer);
1161
	    align(&answer);
1162

1163
#ifndef NANO_TINY
1164
1165
1166
	    if (execute) {
#ifdef ENABLE_MULTIBUFFER
		if (ISSET(MULTIBUFFER))
1167
		    /* Open a blank buffer. */
1168
		    open_buffer("", FALSE);
1169
1170
1171
#endif

		/* Save the command's output in the current buffer. */
1172
		execute_command(answer);
1173
1174
1175
1176
1177
1178
1179
1180
1181

#ifdef ENABLE_MULTIBUFFER
		if (ISSET(MULTIBUFFER)) {
		    /* Move back to the beginning of the first line of
		     * the buffer. */
		    openfile->current = openfile->fileage;
		    openfile->current_x = 0;
		    openfile->placewewant = 0;
		}
1182
#endif
1183
1184
	    } else {
#endif /* !NANO_TINY */
1185
1186
		/* Make sure the path to the file specified in answer is
		 * tilde-expanded. */
1187
1188
		answer = mallocstrassn(answer,
			real_dir_from_tilde(answer));
1189
1190
1191

		/* Save the file specified in answer in the current
		 * buffer. */
1192
		open_buffer(answer, TRUE);
1193
#ifndef NANO_TINY
1194
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1195
#endif
1196

1197
#ifdef ENABLE_MULTIBUFFER
1198
1199
1200
	    if (ISSET(MULTIBUFFER))
		/* Update the screen to account for the current
		 * buffer. */
1201
		display_buffer();
1202
	    else
Chris Allegretta's avatar
Chris Allegretta committed
1203
#endif
1204
	    {
1205
		filestruct *top_save = openfile->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
1206

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

		/* Update the current x-coordinate to account for the
1214
1215
1216
1217
		 * 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. */
1218
		openfile->current_x = strlen(openfile->filebot->data);
1219
1220
1221
1222
		if (openfile->fileage == openfile->filebot) {
#ifndef NANO_TINY
		    if (openfile->mark_set) {
			openfile->mark_begin = openfile->current;
1223
			if (!right_side_up)
1224
1225
1226
1227
			    openfile->mark_begin_x +=
				openfile->current_x;
		    }
#endif
1228
		    openfile->current_x += current_x_save;
1229
		}
1230
#ifndef NANO_TINY
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
		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;
		    }
		}
1241
#endif
1242
1243
1244

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

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

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

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

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

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

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

1270
1271
1272
	    break;
	}
    }
1273
    shortcut_init(FALSE);
1274
1275

    free(ans);
1276
1277
}

1278
1279
1280
/* 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. */
1281
void do_insertfile_void(void)
1282
{
1283
1284
1285
1286
1287
1288

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

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

    display_main_list();
}

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

1316
1317
    if (origpath == NULL)
    	return NULL;
1318

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

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

1329
1330
1331
1332
1333
	d_here = getcwd(d_here, PATH_MAX + 1);
    }

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

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

1346
    d_there = real_dir_from_tilde(origpath);
1347

1348
1349
1350
    /* 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
1351
1352
    path_only = (stat(d_there, &fileinfo) != -1 &&
	S_ISDIR(fileinfo.st_mode));
1353

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

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

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

1367
1368
1369
1370
    /* 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);
1371

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

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

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

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

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

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

1435
    return d_there;
1436
}
1437

1438
1439
1440
/* 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. */
1441
char *check_writable_directory(const char *path)
1442
{
1443
1444
    char *full_path = get_full_path(path);

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

1449
1450
1451
    /* 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
1452
	full_path[strlen(full_path) - 1] != '/') {
1453
	free(full_path);
1454
	return NULL;
1455
    }
1456

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

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

1472
1473
    assert(f != NULL);

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

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

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

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

1493
1494
1495
1496
1497
1498
1499
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

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

1505
1506
    umask(original_umask);

1507
    return full_tempdir;
1508
}
1509
1510

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

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

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

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

1542
    char *fullpath;
1543
    bool retval = FALSE;
1544
    const char *whereami1, *whereami2 = NULL;
1545

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

1550
    assert(full_operating_dir != NULL);
1551
1552

    fullpath = get_full_path(currpath);
1553

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

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

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

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

1582
#ifndef NANO_TINY
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
/* Although this sucks, it sucks less than having a single 'my system is messed up
 * and I'm blanket allowing insecure file writing operations.
 */

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

1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
void init_backup_dir(void)
{
    char *full_backup_dir;

    if (backup_dir == NULL)
	return;

    full_backup_dir = get_full_path(backup_dir);

    /* If get_full_path() failed or the backup directory is
     * inaccessible, unset backup_dir. */
    if (full_backup_dir == NULL ||
	full_backup_dir[strlen(full_backup_dir) - 1] != '/') {
	free(full_backup_dir);
	free(backup_dir);
	backup_dir = NULL;
    } else {
	free(backup_dir);
	backup_dir = full_backup_dir;
    }
}
#endif

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

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

1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
    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
1645

1646
1647
1648
1649
    if (fclose(inn) == EOF)
	retval = -1;
    if (fclose(out) == EOF)
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1650

1651
1652
1653
    return retval;
}

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

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

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

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

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

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

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

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

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

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

1735
    /* Save the state of the file at the end of the symlink (if there is
1736
     * one). */
1737
    realexists = (stat(realname, &st) != -1);
1738

1739
#ifndef NANO_TINY
1740
    /* if we have not stat()d this file before (say, the user just
1741
1742
     * specified it interactively), stat and save the value
     * or else we will chase null pointers when we do
1743
     * modtime checks, preserve file times, etc. during backup */
1744
1745
    if (openfile->current_stat == NULL && !tmp && realexists)
	stat(realname, openfile->current_stat);
1746

1747
    /* We backup only if the backup toggle is set, the file isn't
1748
1749
1750
1751
     * temporary, and the file already exists.  Furthermore, if we
     * aren't appending, prepending, or writing a selection, we backup
     * only if the file has not been modified by someone else since nano
     * opened it. */
1752
    if (ISSET(BACKUP_FILE) && !tmp && realexists && ((append !=
1753
1754
	OVERWRITE || openfile->mark_set) || (openfile->current_stat &&
	openfile->current_stat->st_mtime == st.st_mtime))) {
1755
	int backup_fd;
1756
	FILE *backup_file;
1757
	char *backupname;
1758
	struct utimbuf filetime;
1759
	int copy_status;
1760
	int backup_cflags;
1761

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

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

1770
1771
1772
	    if (f == NULL) {
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1773
		beep();
1774
1775
1776
1777
		/* If we can't read from the original file, go on, since
		 * only saving the original file is better than saving
		 * nothing. */
		goto skip_backup;
1778
	    }
1779
1780
	}

1781
	/* If backup_dir is set, we set backupname to
1782
1783
1784
1785
	 * backup_dir/backupname~[.number], where backupname is the
	 * canonicalized absolute pathname of realname with every '/'
	 * replaced with a '!'.  This means that /home/foo/file is
	 * backed up in backup_dir/!home!foo!file~[.number]. */
1786
	if (backup_dir != NULL) {
1787
	    char *backuptemp = get_full_path(realname);
1788

1789
	    if (backuptemp == NULL)
1790
1791
1792
1793
1794
1795
		/* If get_full_path() failed, we don't have a
		 * canonicalized absolute pathname, so just use the
		 * filename portion of the pathname.  We use tail() so
		 * that e.g. ../backupname will be backed up in
		 * backupdir/backupname~ instead of
		 * backupdir/../backupname~. */
1796
		backuptemp = mallocstrcpy(NULL, tail(realname));
1797
	    else {
1798
1799
		size_t i = 0;

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

	    backupname = charalloc(strlen(backup_dir) +
1807
1808
1809
1810
		strlen(backuptemp) + 1);
	    sprintf(backupname, "%s%s", backup_dir, backuptemp);
	    free(backuptemp);
	    backuptemp = get_next_filename(backupname, "~");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1811
	    if (*backuptemp == '\0') {
1812
		statusbar(_("Error writing backup file %s: %s"), backupname,
1813
1814
1815
		    _("Too many backup files?"));
		free(backuptemp);
		free(backupname);
1816
1817
1818
1819
1820
		/* If we can't write to the backup, 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;
1821
1822
1823
1824
	    } else {
		free(backupname);
		backupname = backuptemp;
	    }
1825
1826
1827
1828
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1829

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

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

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

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

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

1874
1875
1876
1877
	if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1
		&& !ISSET(INSECURE_BACKUP)) {
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1878
	    statusbar(_("Error writing backup file %s: %s"), backupname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1879
		strerror(errno));
1880
	    free(backupname);
1881
	    fclose(backup_file);
1882
1883
1884
1885
1886
	    /* 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;
1887
1888
1889
	}

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

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

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

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

1916
1917
	free(backupname);
    }
1918

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

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

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

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

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

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

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

1958
	tempname = safe_tempfile(&f);
1959

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2045
	/* If we're on the last line of the file, don't write a newline
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2046
2047
2048
2049
2050
2051
2052
	 * 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 {
2053
#ifndef NANO_TINY
2054
2055
2056
2057
	    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
2058
			strerror(errno));
2059
2060
2061
		    fclose(f);
		    goto cleanup_and_exit;
		}
2062
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2063

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

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

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

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

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

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

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

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

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

2132
#ifndef NANO_TINY
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2133
2134
2135
2136
2137
	/* Update current_stat to reference the file as it is now. */
	if (openfile->current_stat == NULL)
	    openfile->current_stat =
		(struct stat *)nmalloc(sizeof(struct stat));
	stat(realname, openfile->current_stat);
2138
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2139

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

2147
    retval = TRUE;
2148
2149
2150

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

2154
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2155
2156
}

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

2171
2172
    assert(openfile->mark_set);

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

2179
2180
2181
2182
2183
2184
    /* 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')))
2185
	new_magicline();
2186

2187
    retval = write_file(name, f_open, tmp, append, TRUE);
2188

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

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

2198
    if (old_modified)
2199
2200
2201
2202
	set_modified();

    return retval;
}
2203

2204
#endif /* !NANO_TINY */
2205

2206
2207
2208
/* 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
2209
2210
 * TEMP_FILE flag is set.  Return TRUE on success or FALSE on error. */
bool do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
2211
{
2212
    int i;
2213
    append_type append = OVERWRITE;
2214
    char *ans;
2215
	/* The last answer the user typed at the statusbar prompt. */
2216
#ifdef NANO_EXTRA
2217
    static bool did_credits = FALSE;
2218
#endif
2219
2220
    bool retval = FALSE, meta_key = FALSE, func_key = FALSE;
    const sc *s;
Chris Allegretta's avatar
Chris Allegretta committed
2221

2222
    currmenu = MWRITEFILE;
2223

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

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

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

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

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

2248
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2249

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

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

2286
2287
	/* If the filename or command begins with a newline (i.e. an
	 * encoded null), treat it as though it's blank. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2288
	if (i < 0 || *answer == '\n') {
2289
	    statusbar(_("Cancelled"));
2290
	    retval = FALSE;
2291
2292
2293
	    break;
	} else {
	    ans = mallocstrcpy(ans, answer);
2294
            s = get_shortcut(currmenu, &i, &meta_key, &func_key);
Chris Allegretta's avatar
Chris Allegretta committed
2295

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

2300
2301
		if (tmp == NULL)
		    continue;
2302

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

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

#ifdef NANO_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2337
2338
2339
2340
2341
2342
2343
2344
	    /* 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) {
2345
		do_credits();
2346
		did_credits = TRUE;
2347
		retval = FALSE;
2348
2349
		break;
	    }
2350
#endif
2351

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

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

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

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

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

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

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

2420
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2421

2422
2423
2424
2425
2426
	    /* Convert newlines to nulls, just before we save the
	     * file. */
	    sunder(answer);
	    align(&answer);

2427
	    /* Here's where we allow the selected text to be written to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2428
2429
2430
	     * 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. */
2431
2432
2433
2434
	    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
2435
#endif
2436
		write_file(answer, NULL, FALSE, append, FALSE);
2437

2438
2439
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2440
    }
2441
2442

    free(ans);
2443

2444
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2445
2446
}

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

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

2461
    assert(buf != NULL);
2462

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2463
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2464
	size_t i = 1;
2465
	char *tilde_dir;
2466

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

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

2478
2479
2480
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

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

2490
2491
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2492

2493
2494
2495
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2496

2497
    return retval;
2498
2499
}

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

2516
2517
2518
2519
    /* Standard function brain damage: We should be sorting
     * alphabetically and case-insensitively according to the current
     * locale, but there's no standard strcasecoll() function, so we
     * have to use multibyte strcasecmp() instead, */
2520
    return mbstrcasecmp(a, b);
2521
}
2522
2523
2524
2525
2526

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

2529
2530
2531
2532
    for (; len > 0; len--)
	free(array[len - 1]);
    free(array);
}
2533
2534
#endif

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

    assert(buf != NULL);
2544

2545
2546
2547
2548
    dirptr = real_dir_from_tilde(buf);

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

2550
    free(dirptr);
2551

2552
    return retval;
2553
}
Chris Allegretta's avatar
Chris Allegretta committed
2554

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

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

2581
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2582

2583
    *num_matches = 0;
2584

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

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

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

2607
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2608
2609
}

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

2621
    assert(dirname != NULL && num_matches != NULL);
2622

2623
    *num_matches = 0;
2624
    null_at(&dirname, buf_len);
2625
2626
2627
2628
2629
2630
2631
2632

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

2642
    assert(dirname[strlen(dirname) - 1] == '/');
2643

Chris Allegretta's avatar
Chris Allegretta committed
2644
    dir = opendir(dirname);
2645

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

    filenamelen = strlen(filename);

2656
    while ((nextdir = readdir(dir)) != NULL) {
2657
2658
	bool skip_match = FALSE;

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

2669
2670
2671
2672
	    char *tmp = charalloc(strlen(dirname) +
		strlen(nextdir->d_name) + 1);
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

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

	    free(tmp);
2686

2687
	    if (skip_match)
2688
		continue;
2689

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

2697
2698
    closedir(dir);
    free(dirname);
2699
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2700

2701
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2702
2703
}

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2715
    *list = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2716

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

2722
2723
2724
2725
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2726

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

2732
2733
2734
    buf_len = strlen(buf);

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

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

2760
	    if (match < num_matches || matches[0][common_len] == '\0')
2761
		break;
2762

2763
	    common_len += parse_mbchar(buf + common_len, NULL, NULL);
2764
	}
2765

2766
2767
2768
	free(match1_mb);
	free(match2_mb);

2769
	mzero = charalloc(lastslash_len + common_len + 1);
2770
2771
2772

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

2774
	common_len += lastslash_len;
2775
	mzero[common_len] = '\0';
2776

2777
	assert(common_len >= *place);
2778

2779
	if (num_matches == 1 && is_dir(mzero)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2780
	    mzero[common_len++] = '/';
2781

2782
2783
	    assert(common_len > *place);
	}
2784

2785
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2786
	    beep();
2787

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

2804
2805
2806
2807
2808
2809
2810
2811
	    /* 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
2812

2813
2814
		if (common_len > COLS - 1) {
		    longest_name = COLS - 1;
2815
2816
		    break;
		}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2817

2818
2819
		if (common_len > longest_name)
		    longest_name = common_len;
2820
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2821

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

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

2829
2830
2831
2832
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2833

2834
2835
	    /* Disable el cursor. */
	    curs_set(0);
2836

2837
2838
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2839

2840
		wmove(edit, editline, (longest_name + 2) *
2841
			(match % ncols));
2842

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

2850
2851
2852
2853
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2854

2855
		if ((match + 1) % ncols == 0)
2856
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2857
	    }
2858

2859
	    wnoutrefresh(edit);
2860
	    *list = TRUE;
2861
2862
2863
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2864
2865
    }

2866
    free_chararray(matches, num_matches);
2867

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

    /* Enable el cursor. */
2874
    curs_set(1);
2875

2876
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2877
}
2878
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2879

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

2886
2887
    if (tmp == NULL)
	tmp = foo;
2888
    else
2889
2890
2891
2892
2893
	tmp++;

    return tmp;
}

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

2902
2903
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2904

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

2910
2911
2912
2913
2914
2915
    return newstr;

}

char *histfilename(void)
{
2916
    return construct_filename("/.nano/search_history");
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
}

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

char *poshistfilename(void)
{
2929
    return construct_filename("/.nano/filepos_history");
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
}



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

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

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

}

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

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

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


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


Chris Allegretta's avatar
Chris Allegretta committed
2988

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

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

3009
	    while ((read = getline(&line, &buf_len, hist)) >= 0) {
3010
3011
3012
3013
3014
3015
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
3016
3017
		    update_history(history, line);
		} else
3018
3019
		    history = &replace_history;
	    }
3020

3021
	    fclose(hist);
3022
	    free(line);
3023
3024
	    if (search_history->prev != NULL)
		last_search = mallocstrcpy(NULL, search_history->prev->data);
3025
	}
3026
	free(nanohist);
3027
	free(legacyhist);
3028
3029
3030
    }
}

3031
3032
3033
/* 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. */
3034
bool writehist(FILE *hist, filestruct *h)
3035
{
3036
    filestruct *p;
3037

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

3043
	sunder(p->data);
3044

3045
	if (fwrite(p->data, sizeof(char), p_len, hist) < p_len ||
3046
3047
		putc('\n', hist) == EOF)
	    return FALSE;
3048
    }
3049

3050
    return TRUE;
3051
3052
}

3053
/* Save histories to ~/.nano/search_history. */
3054
3055
void save_history(void)
{
3056
    char *nanohist;
3057

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3058
    /* Don't save unchanged or empty histories. */
3059
3060
    if (!history_has_changed() || (searchbot->lineno == 1 &&
	replacebot->lineno == 1))
3061
3062
	return;

3063
3064
3065
3066
    nanohist = histfilename();

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

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

3076
3077
	    if (!writehist(hist, searchage) || !writehist(hist,
		replaceage))
3078
		history_error(N_("Error writing %s: %s"), nanohist,
3079
			strerror(errno));
3080

3081
3082
	    fclose(hist);
	}
3083

3084
3085
3086
	free(nanohist);
    }
}
3087
3088
3089
3090
3091
3092


/* Analogs for the POS history */
void save_poshistory(void)
{
    char *poshist;
3093
3094
    char *statusstr = NULL;
    poshiststruct *posptr;
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108

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

3109
3110
3111
3112
            for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
		statusstr = charalloc(strlen(posptr->filename) + 2 * sizeof(ssize_t) + 4);
		sprintf(statusstr, "%s %d %d\n", posptr->filename, (int) posptr->lineno,
			(int) posptr->xno);
3113
3114
3115
		if (fwrite(statusstr, sizeof(char), strlen(statusstr), hist) < strlen(statusstr))
		    history_error(N_("Error writing %s: %s"), poshist,
			strerror(errno));
3116
		free(statusstr);
3117
3118
3119
3120
3121
3122
3123
	    }
	    fclose(hist);
	}
	free(poshist);
    }
}

3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
/* Update the POS history, given a filename line and column.
 * If no entry is found, add a new entry on the end
 */
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
   poshiststruct *posptr, *posprev = NULL;
   char *fullpath = get_full_path(filename);

    if (fullpath == NULL)
        return;

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

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

3146
    posptr = (poshiststruct *) nmalloc(sizeof(poshiststruct));
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
    posptr->filename = mallocstrcpy(NULL, fullpath);
    posptr->lineno = lineno;
    posptr->xno = xpos;
    posptr->next = NULL;

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

    free(fullpath);
}


/* Check the POS history to see if file matches
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
 * an existing entry.  If so return 1 and set line and column
 * to the right values  Otherwise return 0
 */
int check_poshistory(const char *file, ssize_t *line, ssize_t *column)
{
    poshiststruct *posptr;
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
    	return 0;

    for (posptr = poshistory; posptr != NULL; posptr = posptr->next) {
	if (!strcmp(posptr->filename, fullpath)) {
	    *line = posptr->lineno;
	    *column = posptr->xno;
3177
	    free(fullpath);
3178
3179
3180
	    return 1;
	}
    }
3181
    free(fullpath);
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
    return 0;
}


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

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

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

	    /* See if we can find the file we're currently editing */
	    while ((read = getline(&line, &buf_len, hist)) >= 0) {
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
		}
		lineptr = parse_next_word(line);
		xptr = parse_next_word(lineptr);
		lineno = atoi(lineptr);
		xno = atoi(xptr);
		if (poshistory == NULL) {
3222
		    poshistory = (poshiststruct *) nmalloc(sizeof(poshiststruct));
3223
3224
3225
3226
3227
		    poshistory->filename = mallocstrcpy(NULL, line);
		    poshistory->lineno = lineno;
		    poshistory->xno = xno;
		    poshistory->next = NULL;
		} else {
3228
		    for (posptr = poshistory; posptr->next != NULL; posptr = posptr->next)
3229
			;
3230
		    posptr->next = (poshiststruct *) nmalloc(sizeof(poshiststruct));
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
		    posptr->next->filename = mallocstrcpy(NULL, line);
		    posptr->next->lineno = lineno;
		    posptr->next->xno = xno;
		    posptr->next->next = NULL;
		}

	    }

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

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