files.c 90.4 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/**************************************************************************
2
 *   files.c  --  This file is part of GNU nano.                          *
Chris Allegretta's avatar
Chris Allegretta committed
3
 *                                                                        *
4
 *   Copyright (C) 1999-2011, 2013-2017 Free Software Foundation, Inc.    *
5
6
 *   Copyright (C) 2015, 2016 Benno Schulenberg                           *
 *                                                                        *
7
8
9
10
 *   GNU nano is free software: you can redistribute it and/or modify     *
 *   it under the terms of the GNU General Public License as published    *
 *   by the Free Software Foundation, either version 3 of the License,    *
 *   or (at your option) any later version.                               *
Chris Allegretta's avatar
Chris Allegretta committed
11
 *                                                                        *
12
13
14
15
 *   GNU nano 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
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
18
 *   along with this program.  If not, see http://www.gnu.org/licenses/.  *
Chris Allegretta's avatar
Chris Allegretta committed
19
20
21
 *                                                                        *
 **************************************************************************/

22
#include "proto.h"
23

Chris Allegretta's avatar
Chris Allegretta committed
24
#include <stdio.h>
25
#include <string.h>
Chris Allegretta's avatar
Chris Allegretta committed
26
#include <unistd.h>
27
#include <utime.h>
Chris Allegretta's avatar
Chris Allegretta committed
28
29
#include <fcntl.h>
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
#include <ctype.h>
31
#ifdef HAVE_PWD_H
32
#include <pwd.h>
33
#endif
34
#include <libgen.h>
Chris Allegretta's avatar
Chris Allegretta committed
35

36
37
#define LOCKBUFSIZE 8192

38
39
/* Verify that the containing directory of the given filename exists. */
bool has_valid_path(const char *filename)
40
{
41
42
    char *namecopy = mallocstrcpy(NULL, filename);
    char *parentdir = dirname(namecopy);
43
    struct stat parentinfo;
44
    bool validity = FALSE;
45

46
47
    if (stat(parentdir, &parentinfo) == -1) {
	if (errno == ENOENT)
48
	    statusline(ALERT, _("Directory '%s' does not exist"), parentdir);
49
	else
50
	    statusline(ALERT, _("Path '%s': %s"), parentdir, strerror(errno));
51
    } else if (!S_ISDIR(parentinfo.st_mode))
52
	statusline(ALERT, _("Path '%s' is not a directory"), parentdir);
53
    else if (access(parentdir, X_OK) == -1)
54
	statusline(ALERT, _("Path '%s' is not accessible"), parentdir);
55
56
    else if (ISSET(LOCKING) && access(parentdir, W_OK) == -1)
	statusline(MILD, _("Directory '%s' is not writable"), parentdir);
57
58
    else
	validity = TRUE;
59

60
    free(namecopy);
61

62
    return validity;
63
64
}

65
/* Add an item to the circular list of openfile structs. */
66
void make_new_buffer(void)
67
{
68
    openfilestruct *newnode = make_new_opennode();
69

70
    if (openfile == NULL) {
71
	/* Make the first open file the only element in the list. */
72
73
	newnode->prev = newnode;
	newnode->next = newnode;
74
    } else {
75
76
77
78
79
80
	/* Add the new open file after the current one in the list. */
	newnode->prev = openfile;
	newnode->next = openfile->next;
	openfile->next->prev = newnode;
	openfile->next = newnode;

81
	/* There is more than one file open: show "Close" in help lines. */
82
	exitfunc->desc = close_tag;
Chris Allegretta's avatar
Chris Allegretta committed
83
    }
84

85
86
87
    /* Make the new buffer the current one, and start initializing it. */
    openfile = newnode;

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

90
    initialize_buffer_text();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
91

92
    openfile->placewewant = 0;
93
    openfile->current_y = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
94

95
    openfile->modified = FALSE;
96
#ifndef NANO_TINY
97
    openfile->mark_set = FALSE;
98
99
    openfile->mark_begin = NULL;
    openfile->mark_begin_x = 0;
100
    openfile->kind_of_mark = SOFTMARK;
101

102
    openfile->fmt = NIX_FILE;
103

104
105
    openfile->undotop = NULL;
    openfile->current_undo = NULL;
106
    openfile->last_action = OTHER;
107
108

    openfile->current_stat = NULL;
109
    openfile->lock_filename = NULL;
110
#endif
111
#ifndef DISABLE_COLOR
112
    openfile->syntax = NULL;
113
114
    openfile->colorstrings = NULL;
#endif
115
}
Chris Allegretta's avatar
Chris Allegretta committed
116

117
/* Initialize the text and pointers of the current openfile struct. */
118
void initialize_buffer_text(void)
119
{
120
121
    openfile->fileage = make_new_node(NULL);
    openfile->fileage->data = mallocstrcpy(NULL, "");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
122

123
124
125
126
    openfile->filebot = openfile->fileage;
    openfile->edittop = openfile->fileage;
    openfile->current = openfile->fileage;

127
    openfile->firstcolumn = 0;
128
    openfile->current_x = 0;
129
    openfile->totsize = 0;
130
131
}

132
133
134
135
136
137
138
139
140
141
142
/* Mark the current file as modified if it isn't already, and then
 * update the titlebar to display the file's new status. */
void set_modified(void)
{
    if (openfile->modified)
	return;

    openfile->modified = TRUE;
    titlebar(NULL);

#ifndef NANO_TINY
143
    if (!ISSET(LOCKING) || openfile->filename[0] == '\0')
144
145
	return;

146
    if (openfile->lock_filename != NULL) {
147
	char *fullname = get_full_path(openfile->filename);
148

149
150
151
152
153
154
	write_lockfile(openfile->lock_filename, fullname, TRUE);
	free(fullname);
    }
#endif
}

155
#ifndef NANO_TINY
156
157
158
159
160
161
162
163
164
/* Actually write the lockfile.  This function will ALWAYS annihilate
 * any previous version of the file.  We'll borrow INSECURE_BACKUP here
 * to decide about lockfile paranoia here as well...
 *
 * Args:
 *     lockfilename: file name for lock
 *     origfilename: name of the file the lock is for
 *     modified: whether to set the modified bit in the file
 *
165
 * Returns 1 on success, and 0 on failure (but continue anyway). */
166
167
int write_lockfile(const char *lockfilename, const char *origfilename, bool modified)
{
168
#ifdef HAVE_PWD_H
169
170
171
172
173
    int cflags, fd;
    FILE *filestream;
    pid_t mypid;
    uid_t myuid;
    struct passwd *mypwuid;
174
    struct stat fileinfo;
175
176
    char *lockdata = charalloc(1024);
    char myhostname[32];
177
178
    size_t lockdatalen = 1024;
    size_t wroteamt;
179

180
    mypid = getpid();
181
    myuid = geteuid();
182
183

    /* First run things that might fail before blowing away the old state. */
184
    if ((mypwuid = getpwuid(myuid)) == NULL) {
185
	/* TRANSLATORS: Keep the next eight messages at most 76 characters. */
186
	statusline(MILD, _("Couldn't determine my identity for lock file "
187
				"(getpwuid() failed)"));
188
	goto free_the_data;
189
190
191
    }

    if (gethostname(myhostname, 31) < 0) {
192
193
194
	if (errno == ENAMETOOLONG)
	    myhostname[31] = '\0';
	else {
195
	    statusline(MILD, _("Couldn't determine hostname for lock file: %s"),
196
			strerror(errno));
197
	    goto free_the_data;
198
	}
199
200
    }

201
    /* If the lockfile exists, try to delete it. */
202
203
    if (stat(lockfilename, &fileinfo) != -1)
	if (delete_lockfile(lockfilename) < 0)
204
	    goto free_the_data;
205
206

    if (ISSET(INSECURE_BACKUP))
207
	cflags = O_WRONLY | O_CREAT | O_APPEND;
208
    else
209
	cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;
210

211
    /* Try to create the lockfile. */
212
    fd = open(lockfilename, cflags,
213
		S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
214
    if (fd < 0) {
215
	statusline(MILD, _("Error writing lock file %s: %s"),
216
			lockfilename, strerror(errno));
217
	goto free_the_data;
218
    }
219

220
    /* Try to associate a stream with the now open lockfile. */
221
222
    filestream = fdopen(fd, "wb");

223
    if (filestream == NULL) {
224
	statusline(MILD, _("Error writing lock file %s: %s"), lockfilename,
225
			strerror(errno));
226
	goto free_the_data;
227
228
    }

229
    /* This is the lock data we will store:
230
231
232
233
     *
     * byte 0        - 0x62
     * byte 1        - 0x30
     * bytes 2-12    - program name which created the lock
234
     * bytes 24-27   - PID (little endian) of creator process
235
236
237
238
239
240
241
242
243
244
     * bytes 28-44   - username of who created the lock
     * bytes 68-100  - hostname of where the lock was created
     * bytes 108-876 - filename the lock is for
     * byte 1007     - 0x55 if file is modified
     *
     * Looks like VIM also stores undo state in this file, so we're
     * gonna have to figure out how to slap a 'OMG don't use recover on
     * our lockfile' message in here...
     *
     * This is likely very wrong, so this is a WIP. */
245
    memset(lockdata, 0, lockdatalen);
246
247
248
    lockdata[0] = 0x62;
    lockdata[1] = 0x30;
    lockdata[24] = mypid % 256;
249
250
251
    lockdata[25] = (mypid / 256) % 256;
    lockdata[26] = (mypid / (256 * 256)) % 256;
    lockdata[27] = mypid / (256 * 256 * 256);
252
    snprintf(&lockdata[2], 11, "nano %s", VERSION);
253
254
    strncpy(&lockdata[28], mypwuid->pw_name, 16);
    strncpy(&lockdata[68], myhostname, 31);
255
256
    if (origfilename != NULL)
	strncpy(&lockdata[108], origfilename, 768);
257
    if (modified == TRUE)
258
	lockdata[1007] = 0x55;
259
260
261

    wroteamt = fwrite(lockdata, sizeof(char), lockdatalen, filestream);
    if (wroteamt < lockdatalen) {
262
	statusline(MILD, _("Error writing lock file %s: %s"),
263
			lockfilename, ferror(filestream));
264
	goto free_the_data;
265
266
267
    }

#ifdef DEBUG
268
    fprintf(stderr, "In write_lockfile(), write successful (wrote %lu bytes)\n", (unsigned long)wroteamt);
269
#endif
270
271

    if (fclose(filestream) == EOF) {
272
	statusline(MILD, _("Error writing lock file %s: %s"),
273
			lockfilename, strerror(errno));
274
	goto free_the_data;
275
276
    }

277
    openfile->lock_filename = (char *) lockfilename;
278

Benno Schulenberg's avatar
Benno Schulenberg committed
279
    free(lockdata);
280
    return 1;
Benno Schulenberg's avatar
Benno Schulenberg committed
281

282
  free_the_data:
Benno Schulenberg's avatar
Benno Schulenberg committed
283
    free(lockdata);
284
    return 0;
285
286
287
#else
    return 1;
#endif
288
289
}

290
/* Delete the lockfile.  Return -1 if unsuccessful, and 1 otherwise. */
291
292
293
int delete_lockfile(const char *lockfilename)
{
    if (unlink(lockfilename) < 0 && errno != ENOENT) {
294
	statusline(MILD, _("Error deleting lock file %s: %s"), lockfilename,
295
			strerror(errno));
296
	return -1;
297
298
299
300
    }
    return 1;
}

301
302
/* Deal with lockfiles.  Return -1 on refusing to override the lockfile,
 * and 1 on successfully creating it; 0 means we were not successful in
303
 * creating the lockfile but we should continue to load the file. */
304
305
int do_lockfile(const char *filename)
{
306
307
    char *namecopy = (char *) mallocstrcpy(NULL, filename);
    char *secondcopy = (char *) mallocstrcpy(NULL, filename);
Benno Schulenberg's avatar
Benno Schulenberg committed
308
    size_t locknamesize = strlen(filename) + strlen(locking_prefix)
309
		+ strlen(locking_suffix) + 3;
Benno Schulenberg's avatar
Benno Schulenberg committed
310
    char *lockfilename = charalloc(locknamesize);
311
    static char lockprog[11], lockuser[17];
312
    struct stat fileinfo;
313
    int lockfd, lockpid, retval = -1;
314

315
316
317
    snprintf(lockfilename, locknamesize, "%s/%s%s%s", dirname(namecopy),
		locking_prefix, basename(secondcopy), locking_suffix);
    free(secondcopy);
318
319
#ifdef DEBUG
    fprintf(stderr, "lock file name is %s\n", lockfilename);
320
#endif
321
    if (stat(lockfilename, &fileinfo) != -1) {
322
323
	size_t readtot = 0;
	size_t readamt = 0;
324
	char *lockbuf, *question, *pidstring, *postedname, *promptstr;
325
	int room, response;
326

327
	if ((lockfd = open(lockfilename, O_RDONLY)) < 0) {
328
	    statusline(MILD, _("Error opening lock file %s: %s"),
329
			lockfilename, strerror(errno));
330
	    goto free_the_name;
331
	}
332

333
	lockbuf = charalloc(LOCKBUFSIZE);
334
	do {
335
	    readamt = read(lockfd, &lockbuf[readtot], LOCKBUFSIZE - readtot);
336
	    readtot += readamt;
337
	} while (readamt > 0 && readtot < LOCKBUFSIZE);
338

339
340
	close(lockfd);

341
	if (readtot < 48) {
342
	    statusline(MILD, _("Error reading lock file %s: "
343
			"Not enough data read"), lockfilename);
344
345
	    free(lockbuf);
	    goto free_the_name;
346
	}
347

348
	strncpy(lockprog, &lockbuf[2], 10);
349
350
	lockpid = (((unsigned char)lockbuf[27] * 256 + (unsigned char)lockbuf[26]) * 256 +
			(unsigned char)lockbuf[25]) * 256 + (unsigned char)lockbuf[24];
351
	strncpy(lockuser, &lockbuf[28], 16);
352
353
	free(lockbuf);

354
355
356
	pidstring = charalloc(11);
	sprintf (pidstring, "%u", (unsigned int)lockpid);

357
#ifdef DEBUG
358
359
360
	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);
361
#endif
362
	/* TRANSLATORS: The second %s is the name of the user, the third that of the editor. */
363
364
365
	question = _("File %s is being edited (by %s with %s, PID %s); continue?");
	room = COLS - strlenpt(question) + 7 - strlenpt(lockuser) -
				strlenpt(lockprog) - strlenpt(pidstring);
366
	if (room < 4)
367
368
369
370
371
372
373
374
375
376
	    postedname = mallocstrcpy(NULL, "_");
	else if (room < strlenpt(filename)) {
	    char *fragment = display_string(filename,
				strlenpt(filename) - room + 3, room, FALSE);
	    postedname = charalloc(strlen(fragment) + 4);
	    strcpy(postedname, "...");
	    strcat(postedname, fragment);
	    free(fragment);
	} else
	    postedname = mallocstrcpy(NULL, filename);
377

378
	/* Allow extra space for username (14), program name (8), PID (8),
379
	 * and terminating \0 (1), minus the %s (2) for the file name. */
380
381
	promptstr = charalloc(strlen(question) + 29 + strlen(postedname));
	sprintf(promptstr, question, postedname, lockuser, lockprog, pidstring);
382
	free(postedname);
383
	free(pidstring);
384

385
	response = do_yesno_prompt(FALSE, promptstr);
386
387
	free(promptstr);

388
	if (response < 1) {
389
	    blank_statusbar();
390
	    goto free_the_name;
391
	}
392
    }
393
394
395
396

    retval = write_lockfile(lockfilename, filename, FALSE);

  free_the_name:
397
    free(namecopy);
398
399
400
401
    if (retval < 1)
	free(lockfilename);

    return retval;
402
}
403
404
405
406
407
408
409
410
411
412
413
414
415
416

/* Perform a stat call on the given filename, allocating a stat struct
 * if necessary.  On success, *pstat points to the stat's result.  On
 * failure, *pstat is freed and made NULL. */
void stat_with_alloc(const char *filename, struct stat **pstat)
{
    if (*pstat == NULL)
	*pstat = (struct stat *)nmalloc(sizeof(struct stat));

    if (stat(filename, *pstat) != 0) {
	free(*pstat);
	*pstat = NULL;
    }
}
417
#endif /* !NANO_TINY */
418

419
420
421
/* This does one of three things.  If the filename is "", just create a new
 * empty buffer.  Otherwise, read the given file into the existing buffer,
 * or into a new buffer when MULTIBUFFER is set or there is no buffer yet. */
422
bool open_buffer(const char *filename, bool undoable)
423
{
424
425
    bool new_buffer = (openfile == NULL || ISSET(MULTIBUFFER));
	/* Whether we load into the current buffer or a new one. */
426
427
    char *realname;
	/* The filename after tilde expansion. */
428
429
430
431
    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. */
432

433
434
    assert(filename != NULL);

435
436
437
    /* Display newlines in filenames as ^J. */
    as_an_at = FALSE;

438
439
#ifndef DISABLE_OPERATINGDIR
    if (check_operating_dir(filename, FALSE)) {
440
	statusline(ALERT, _("Can't insert file from outside of %s"),
441
				full_operating_dir);
442
	return FALSE;
443
444
    }
#endif
445

446
447
    realname = real_dir_from_tilde(filename);

448
449
    /* When the specified filename is not empty, and the corresponding
     * file exists, verify that it is a normal file. */
450
451
452
    if (strcmp(filename, "") != 0) {
	struct stat fileinfo;

453
	if (stat(realname, &fileinfo) == 0 && !S_ISREG(fileinfo.st_mode)) {
454
	    if (S_ISDIR(fileinfo.st_mode))
455
		statusline(ALERT, _("\"%s\" is a directory"), realname);
456
	    else
457
		statusline(ALERT, _("\"%s\" is not a normal file"), realname);
458
	    free(realname);
459
	    return FALSE;
460
461
462
	}
    }

463
    /* If we're going to load into a new buffer, first create the new
464
     * buffer and (if possible) lock the corresponding file. */
465
    if (new_buffer) {
466
	make_new_buffer();
467

468
	if (!inhelp && has_valid_path(realname)) {
469
#ifndef NANO_TINY
470
	    if (ISSET(LOCKING) && filename[0] != '\0') {
471
		/* When not overriding an existing lock, discard the buffer. */
472
		if (do_lockfile(realname) < 0) {
473
#ifdef ENABLE_MULTIBUFFER
474
		    close_buffer();
475
#endif
476
477
		    free(realname);
		    return FALSE;
478
		}
479
	    }
480
#endif /* !NANO_TINY */
481
	}
482
483
    }

484
485
486
    /* If the filename isn't blank, and we are not in NOREAD_MODE,
     * open the file.  Otherwise, treat it as a new file. */
    rc = (filename[0] != '\0' && !ISSET(NOREAD_MODE)) ?
487
		open_file(realname, new_buffer, inhelp, &f) : -2;
488

489
490
    /* If we have a file, and we're loading into a new buffer, update
     * the filename. */
491
    if (rc != -1 && new_buffer)
492
	openfile->filename = mallocstrcpy(openfile->filename, realname);
493

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
494
495
    /* If we have a non-new file, read it in.  Then, if the buffer has
     * no stat, update the stat, if applicable. */
496
    if (rc > 0) {
497
	read_file(f, rc, realname, undoable, new_buffer);
498
#ifndef NANO_TINY
499
	if (openfile->current_stat == NULL)
500
	    stat_with_alloc(realname, &openfile->current_stat);
501
#endif
502
503
    }

504
    /* If we have a file, and we're loading into a new buffer, move back
505
506
     * to the beginning of the first line of the buffer. */
    if (rc != -1 && new_buffer) {
507
	openfile->current = openfile->fileage;
508
509
510
	openfile->current_x = 0;
	openfile->placewewant = 0;
    }
511

512
#ifndef DISABLE_COLOR
513
514
    /* If we're loading into a new buffer, update the colors to account
     * for it, if applicable. */
515
516
517
    if (new_buffer)
	color_update();
#endif
518
    free(realname);
519
    return TRUE;
520
}
521

522
#ifndef DISABLE_SPELLER
523
524
/* Open the specified file, and if that succeeds, blow away the text of
 * the current buffer and read the file contents into its place. */
525
526
527
void replace_buffer(const char *filename)
{
    FILE *f;
528
    int descriptor;
529

530
    assert(filename != NULL && filename[0] != '\0');
531

532
    /* Open the file quietly. */
533
534
535
536
537
    descriptor = open_file(filename, FALSE, TRUE, &f);

    /* If opening failed, forget it. */
    if (descriptor < 0)
	return;
538
539
540
541
542

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

543
544
    /* Insert the processed file into its place. */
    read_file(f, descriptor, filename, FALSE, TRUE);
545
546
547

    /* Put current at a place that is certain to exist. */
    openfile->current = openfile->fileage;
548
}
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580

#ifndef NANO_TINY
/* Open the specified file, and if that succeeds, blow away the text of
 * the current buffer at the given coordinates and read the file
 * contents into its place. */
void replace_marked_buffer(const char *filename, filestruct *top, size_t top_x,
	filestruct *bot, size_t bot_x)
{
    FILE *f;
    int descriptor;
    bool old_no_newlines = ISSET(NO_NEWLINES);
    filestruct *trash_top = NULL;
    filestruct *trash_bot = NULL;

    descriptor = open_file(filename, FALSE, TRUE, &f);

    if (descriptor < 0)
	return;

    /* Don't add a magicline when replacing text in the buffer. */
    SET(NO_NEWLINES);

    /* Throw away the text under the mark, and insert the processed file
     * where the marked text was. */
    extract_buffer(&trash_top, &trash_bot, top, top_x, bot, bot_x);
    free_filestruct(trash_top);
    read_file(f, descriptor, filename, FALSE, TRUE);

    /* Restore the magicline behavior now that we're done fiddling. */
    if (!old_no_newlines)
	UNSET(NO_NEWLINES);
}
581
#endif /* !NANO_TINY */
582
583
#endif /* !DISABLE_SPELLER */

584
585
/* Update the titlebar and the multiline cache to match the current buffer. */
void prepare_for_display(void)
586
{
587
    /* Update the titlebar, since the filename may have changed. */
588
589
    if (!inhelp)
	titlebar(NULL);
590

591
#ifndef DISABLE_COLOR
592
593
594
595
596
    /* If there are multiline coloring regexes, and there is no
     * multiline cache data yet, precalculate it now. */
    if (openfile->syntax && openfile->syntax->nmultis > 0 &&
		openfile->fileage->multidata == NULL)
	precalc_multicolorinfo();
597

598
599
    have_palette = FALSE;
#endif
600
    refresh_needed = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
601
602
}

603
#ifdef ENABLE_MULTIBUFFER
604
605
/* Switch to a neighbouring file buffer; to the next if to_next is TRUE;
 * otherwise, to the previous one. */
606
void switch_to_prevnext_buffer(bool to_next)
Chris Allegretta's avatar
Chris Allegretta committed
607
{
608
    assert(openfile != NULL);
609

610
    /* If only one file buffer is open, say so and get out. */
611
    if (openfile == openfile->next && !inhelp) {
612
	statusbar(_("No more open file buffers"));
613
	return;
Chris Allegretta's avatar
Chris Allegretta committed
614
    }
615

616
617
    /* Switch to the next or previous file buffer. */
    openfile = to_next ? openfile->next : openfile->prev;
618
619

#ifdef DEBUG
620
    fprintf(stderr, "filename is %s\n", openfile->filename);
621
622
#endif

623
624
625
626
627
628
629
630
#ifndef NANO_TINY
    /* When not in softwrap mode, make sure firstcolumn is zero.  It might
     * be nonzero if we had softwrap mode on while in this buffer, and then
     * turned softwrap mode off while in a different buffer. */
    if (!ISSET(SOFTWRAP))
	openfile->firstcolumn = 0;
#endif

631
632
    /* Update titlebar and multiline info to match the current buffer. */
    prepare_for_display();
633

634
    /* Indicate the switch on the statusbar. */
635
636
637
638
    if (!inhelp)
	statusline(HUSH, _("Switched to %s"),
			((openfile->filename[0] == '\0') ?
			_("New Buffer") : openfile->filename));
639
640

#ifdef DEBUG
641
    dump_filestruct(openfile->current);
642
#endif
Chris Allegretta's avatar
Chris Allegretta committed
643
644
}

645
/* Switch to the previous entry in the openfile filebuffer. */
646
void switch_to_prev_buffer_void(void)
647
{
648
    switch_to_prevnext_buffer(FALSE);
649
}
650

651
/* Switch to the next entry in the openfile filebuffer. */
652
void switch_to_next_buffer_void(void)
653
{
654
    switch_to_prevnext_buffer(TRUE);
655
}
656

657
658
659
660
/* Delete an entry from the circular list of open files, and switch to the
 * one after it.  Return TRUE on success, and FALSE if there are no other
 * open buffers. */
bool close_buffer(void)
661
{
662
    assert(openfile != NULL);
663

664
    /* If only one file buffer is open, get out. */
665
    if (openfile == openfile->next)
666
	return FALSE;
667

668
#ifndef DISABLE_HISTORIES
669
670
671
    if (ISSET(POS_HISTORY))
	update_poshistory(openfile->filename,
			openfile->current->lineno, xplustabs() + 1);
672
#endif
673

674
    /* Switch to the next file buffer. */
675
    switch_to_prevnext_buffer(TRUE);
676

677
    /* Close the file buffer we had open before. */
678
    unlink_opennode(openfile->prev);
679

680
681
682
    /* If only one buffer is open now, show Exit in the help lines. */
    if (openfile == openfile->next)
	exitfunc->desc = exit_tag;
683

684
    return TRUE;
685
}
686
#endif /* ENABLE_MULTIBUFFER */
687

688
689
690
691
/* Do a quick permissions check by verifying whether the file is appendable.
 * 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. */
692
693
694
695
696
697
int is_file_writable(const char *filename)
{
    struct stat fileinfo, fileinfo2;
    int fd;
    FILE *f;
    char *full_filename;
698
    bool result = TRUE;
699
700
701
702

    if (ISSET(VIEW_MODE))
	return TRUE;

703
    assert(filename != NULL);
704
705
706
707

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

708
709
    /* Okay, if we can't stat the absolute path due to some component's
     * permissions, just try the relative one. */
710
711
712
    if (full_filename == NULL ||
		(stat(full_filename, &fileinfo) == -1 && stat(filename, &fileinfo2) != -1))
	full_filename = mallocstrcpy(NULL, filename);
713
714

    if ((fd = open(full_filename, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR |
715
716
		S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) == -1 ||
		(f = fdopen(fd, "a")) == NULL)
717
	result = FALSE;
718
    else
719
	fclose(f);
720

721
    close(fd);
722
    free(full_filename);
723
724

    return result;
725
726
}

727
728
729
/* Encode any NUL bytes in the given line of text, which is of length buf_len,
 * and return a dynamically allocated copy of the resultant string. */
char *encode_data(char *buf, size_t buf_len)
730
{
731
    unsunder(buf, buf_len);
732
    buf[buf_len] = '\0';
733

734
    return mallocstrcpy(NULL, buf);
735
}
736

737
/* Read an open file into the current buffer.  f should be set to the
738
 * open file, and filename should be set to the name of the file.
739
740
741
 * undoable means do we want to create undo records to try and undo
 * this.  Will also attempt to check file writability if fd > 0 and
 * checkwritable == TRUE. */
742
743
void read_file(FILE *f, int fd, const char *filename, bool undoable,
		bool checkwritable)
744
{
745
746
    ssize_t was_lineno = openfile->current->lineno;
	/* The line number where we start the insertion. */
747
748
    size_t was_leftedge = 0;
	/* The leftedge where we start the insertion. */
749
750
751
752
753
754
755
    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. */
    char input = '\0';
	/* The current input character. */
    char *buf;
756
757
758
	/* The buffer in which we assemble each line of the file. */
    size_t bufx = MAX_BUF_SIZE;
	/* The allocated size of the line buffer; increased as needed. */
759
760
761
762
    filestruct *topline;
	/* The top of the new buffer where we store the read file. */
    filestruct *bottomline;
	/* The bottom of the new buffer. */
763
764
765
    int input_int;
	/* The current value we read from the file, whether an input
	 * character or EOF. */
766
    bool writable = TRUE;
767
	/* Whether the file is writable (in case we care). */
768
#ifndef NANO_TINY
769
770
    int format = 0;
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
771
#endif
772

773
    buf = charalloc(bufx);
774

775
776
777
#ifndef NANO_TINY
    if (undoable)
	add_undo(INSERT);
778
779
780

    if (ISSET(SOFTWRAP))
	was_leftedge = (xplustabs() / editwincols) * editwincols;
781
782
#endif

783
784
785
786
787
    /* Create an empty buffer. */
    topline = make_new_node(NULL);
    bottomline = topline;

    /* Read the entire file into the new buffer. */
788
789
790
791
792
793
    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') {
794
#ifndef NANO_TINY
795
796
797
798
	    /* 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! */
799
	    if ((num_lines == 0 || format != 0) && !ISSET(NO_CONVERT) &&
800
			len > 0 && buf[len - 1] == '\r') {
801
802
803
804
805
806
		if (format == 0 || format == 2)
		    format++;
	    }
	/* 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! */
807
808
	} else if ((num_lines == 0 || format != 0) && !ISSET(NO_CONVERT) &&
			len > 0 && buf[len - 1] == '\r') {
809
810
811
812
813
	    /* 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;
814
#endif
815
	} else {
816
817
818
	    /* Store the character. */
	    buf[len] = input;

819
820
	    /* Keep track of the total length of the line.  It might have
	     * nulls in it, so we can't just use strlen() later. */
821
	    len++;
822

823
824
	    /* If needed, increase the buffer size, MAX_BUF_SIZE characters at
	     * a time.  Don't bother decreasing it; it is freed at the end. */
825
	    if (len == bufx) {
826
827
		bufx += MAX_BUF_SIZE;
		buf = charealloc(buf, bufx);
828
	    }
829
	    continue;
830
	}
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852

#ifndef NANO_TINY
	/* If it's a DOS or Mac line, strip the '\r' from it. */
	if (len > 0 && buf[len - 1] == '\r' && !ISSET(NO_CONVERT))
	    buf[--len] = '\0';
#endif

	/* Store the data and make a new line. */
	bottomline->data = encode_data(buf, len);
	bottomline->next = make_new_node(bottomline);
	bottomline = bottomline->next;
	num_lines++;

	/* Reset the length in preparation for the next line. */
	len = 0;

#ifndef NANO_TINY
	/* If it happens to be a Mac line, store the character after the \r
	 * as the first character of the next line. */
	if (input != '\n')
	    buf[len++] = input;
#endif
853
854
855
856
857
858
    }

    /* Perhaps this could use some better handling. */
    if (ferror(f))
	nperror(filename);
    fclose(f);
859
    if (fd > 0 && checkwritable) {
860
	close(fd);
861
862
	writable = is_file_writable(filename);
    }
863

864
865
866
867
868
869
870
    /* If the file ended with newline, or it was entirely empty, make the
     * last line blank.  Otherwise, put the last read data in. */
    if (len == 0)
	bottomline->data = mallocstrcpy(NULL, "");
    else {
	bool mac_line_needs_newline = FALSE;

871
#ifndef NANO_TINY
872
873
874
875
876
877
	/* If the final character is '\r', and file conversion isn't disabled,
	 * set format to Mac if we currently think the file is a *nix file, or
	 * to DOS-and-Mac if we currently think it is a DOS file. */
	if (buf[len - 1] == '\r' && !ISSET(NO_CONVERT)) {
	    if (format < 2)
		format += 2;
878

879
880
	    /* Strip the carriage return. */
	    buf[--len] = '\0';
881

882
883
	    /* Indicate we need to put a blank line in after this one. */
	    mac_line_needs_newline = TRUE;
884
	}
885
886
887
888
#endif
	/* Store the data of the final line. */
	bottomline->data = encode_data(buf, len);
	num_lines++;
889

890
891
892
893
	if (mac_line_needs_newline) {
	    bottomline->next = make_new_node(bottomline);
	    bottomline = bottomline->next;
	    bottomline->data = mallocstrcpy(NULL, "");
894
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
895
    }
896

897
    free(buf);
898

899
900
    /* Insert the just read buffer into the current one. */
    ingraft_buffer(topline);
901

902
    /* Set the desired x position at the end of what was inserted. */
903
904
    openfile->placewewant = xplustabs();

905
906
907
908
    /* If we've read a help file, don't give any feedback. */
    if (inhelp)
	return;

909
    if (!writable)
910
	statusline(ALERT, _("File '%s' is unwritable"), filename);
911
#ifndef NANO_TINY
912
    else if (format == 3) {
913
914
915
916
	/* TRANSLATORS: Keep the next four messages at most 78 characters. */
	statusline(HUSH, 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);
917
    } else if (format == 2) {
918
	openfile->fmt = MAC_FILE;
919
920
921
	statusline(HUSH, P_("Read %lu line (Converted from Mac format)",
			"Read %lu lines (Converted from Mac format)",
			(unsigned long)num_lines), (unsigned long)num_lines);
922
    } else if (format == 1) {
923
	openfile->fmt = DOS_FILE;
924
925
926
	statusline(HUSH, P_("Read %lu line (Converted from DOS format)",
			"Read %lu lines (Converted from DOS format)",
			(unsigned long)num_lines), (unsigned long)num_lines);
927
    }
928
#endif
929
    else
930
931
	statusline(HUSH, P_("Read %lu line", "Read %lu lines",
			(unsigned long)num_lines), (unsigned long)num_lines);
932

933
934
    /* If we inserted less than a screenful, don't center the cursor. */
    if (less_than_a_screenful(was_lineno, was_leftedge))
935
936
	focusing = FALSE;

937
#ifndef NANO_TINY
938
939
940
    if (undoable)
	update_undo(INSERT);

941
942
    if (ISSET(MAKE_IT_UNIX))
	openfile->fmt = NIX_FILE;
943
#endif
944
}
Chris Allegretta's avatar
Chris Allegretta committed
945

946
947
/* Open the file with the given name.  If the file does not exist, display
 * "New File" if newfie is TRUE, and say "File not found" otherwise.
948
 * Return -2 if we say "New File", -1 if the file isn't opened, and the
949
 * obtained fd otherwise.  *f is set to the opened file. */
950
int open_file(const char *filename, bool newfie, bool quiet, FILE **f)
951
{
952
    struct stat fileinfo, fileinfo2;
953
    int fd;
954
    char *full_filename;
955

956
    assert(filename != NULL && f != NULL);
957

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

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

    if (stat(full_filename, &fileinfo) == -1) {
968
	if (newfie) {
969
	    if (!quiet)
970
		statusbar(_("New File"));
971
	    free(full_filename);
972
973
	    return -2;
	}
974

975
	statusline(ALERT, _("File \"%s\" not found"), filename);
976
	free(full_filename);
977
978
	return -1;
    }
979

980
981
982
    /* Don't open directories, character files, or block files. */
    if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
				S_ISBLK(fileinfo.st_mode)) {
983
	statusline(ALERT, S_ISDIR(fileinfo.st_mode) ?
984
985
			_("\"%s\" is a directory") :
			_("\"%s\" is a device file"), filename);
986
	free(full_filename);
987
	return -1;
988
989
990
991
992
993
994
995
996
    }

    /* Try opening the file. */
    fd = open(full_filename, O_RDONLY);

    if (fd == -1)
	statusline(ALERT, _("Error reading %s: %s"), filename, strerror(errno));
    else {
	/* The file is A-OK.  Associate a stream with it. */
997
	*f = fdopen(fd, "rb");
998

999
	if (*f == NULL) {
1000
	    statusline(ALERT, _("Error reading %s: %s"), filename, strerror(errno));
1001
	    close(fd);
1002
	} else if (!inhelp)
1003
	    statusbar(_("Reading File"));
1004
    }
1005

1006
1007
    free(full_filename);

1008
1009
1010
    return fd;
}

1011
1012
1013
1014
1015
/* 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)
1016
{
1017
1018
    unsigned long i = 0;
    char *buf;
1019
    size_t wholenamelen;
1020

1021
    assert(name != NULL && suffix != NULL);
1022

1023
    wholenamelen = strlen(name) + strlen(suffix);
1024

1025
1026
1027
    /* Reserve space for: the name plus the suffix plus a dot plus
     * possibly five digits plus a null byte. */
    buf = charalloc(wholenamelen + 7);
1028
    sprintf(buf, "%s%s", name, suffix);
1029

1030
1031
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
1032

1033
1034
	if (stat(buf, &fs) == -1)
	    return buf;
1035
1036
1037

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

1040
	sprintf(buf + wholenamelen, ".%lu", i);
1041
    }
Chris Allegretta's avatar
Chris Allegretta committed
1042

1043
1044
    /* There is no possible save file: blank out the filename. */
    *buf = '\0';
1045

1046
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
1047
1048
}

1049
1050
1051
/* Insert a file into the current buffer, or into a new buffer when
 * the MULTIBUFFER flag is set. */
void do_insertfile(void)
1052
1053
1054
{
    int i;
    const char *msg;
1055
    char *given = mallocstrcpy(NULL, "");
1056
	/* The last answer the user typed at the statusbar prompt. */
1057
#ifndef NANO_TINY
1058
    bool execute = FALSE;
1059
#endif
1060

1061
    /* Display newlines in filenames as ^J. */
1062
1063
    as_an_at = FALSE;

1064
    while (TRUE) {
1065
#ifndef NANO_TINY
1066
	if (execute) {
1067
#ifdef ENABLE_MULTIBUFFER
1068
	    if (ISSET(MULTIBUFFER))
1069
		/* TRANSLATORS: The next four messages are prompts. */
1070
		msg = _("Command to execute in new buffer");
1071
	    else
1072
#endif
1073
		msg = _("Command to execute");
1074
1075
1076
	} else
#endif /* NANO_TINY */
	{
1077
#ifdef ENABLE_MULTIBUFFER
1078
	    if (ISSET(MULTIBUFFER))
1079
1080
1081
1082
		msg = _("File to insert into new buffer [from %s]");
	    else
#endif
		msg = _("File to insert [from %s]");
1083
	}
1084

1085
1086
	present_path = mallocstrcpy(present_path, "./");

1087
	i = do_prompt(TRUE, TRUE,
1088
#ifndef NANO_TINY
1089
		execute ? MEXTCMD :
1090
#endif
1091
		MINSERTFILE, given,
1092
#ifndef DISABLE_HISTORIES
1093
		NULL,
1094
#endif
1095
		edit_refresh, msg,
1096
#ifndef DISABLE_OPERATINGDIR
1097
		operating_dir != NULL ? operating_dir :
1098
#endif
1099
		"./");
1100

1101
	/* If we're in multibuffer mode and the filename or command is
1102
1103
	 * blank, open a new buffer instead of canceling. */
	if (i == -1 || (i == -2 && !ISSET(MULTIBUFFER))) {
1104
1105
1106
	    statusbar(_("Cancelled"));
	    break;
	} else {
1107
1108
	    ssize_t was_current_lineno = openfile->current->lineno;
	    size_t was_current_x = openfile->current_x;
1109
#if !defined(NANO_TINY) || defined(ENABLE_BROWSER) || defined(ENABLE_MULTIBUFFER)
1110
	    functionptrtype func = func_from_key(&i);
1111
#endif
1112
	    given = mallocstrcpy(given, answer);
1113

1114
#ifdef ENABLE_MULTIBUFFER
1115
	    if (func == flip_newbuffer) {
1116
		/* Don't allow toggling when in view mode. */
1117
1118
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
1119
1120
		else
		    beep();
1121
		continue;
1122
	    }
1123
#endif
1124
#ifndef NANO_TINY
1125
	    if (func == flip_execute) {
1126
1127
1128
		execute = !execute;
		continue;
	    }
1129
#endif
1130
#ifdef ENABLE_BROWSER
1131
	    if (func == to_files_void) {
1132
		char *chosen = do_browse_from(answer);
1133

1134
		/* If no file was chosen, go back to the prompt. */
1135
		if (chosen == NULL)
1136
		    continue;
1137

1138
		free(answer);
1139
		answer = chosen;
1140
1141
1142
		i = 0;
	    }
#endif
1143
1144
	    /* If we don't have a file yet, go back to the prompt. */
	    if (i != 0 && (!ISSET(MULTIBUFFER) || i != -2))
1145
		continue;
1146

1147
#ifndef NANO_TINY
1148
	    if (execute) {
1149
#ifdef ENABLE_MULTIBUFFER
1150
		/* When in multibuffer mode, first open a blank buffer. */
1151
		if (ISSET(MULTIBUFFER))
1152
		    open_buffer("", FALSE);
1153
1154
#endif
		/* Save the command's output in the current buffer. */
1155
		execute_command(answer);
1156

1157
#ifdef ENABLE_MULTIBUFFER
1158
		/* If this is a new buffer, put the cursor at the top. */
1159
1160
1161
1162
		if (ISSET(MULTIBUFFER)) {
		    openfile->current = openfile->fileage;
		    openfile->current_x = 0;
		    openfile->placewewant = 0;
1163
1164

		    set_modified();
1165
		}
1166
#endif
1167
	    } else
1168
#endif /* !NANO_TINY */
1169
	    {
1170
1171
		/* Make sure the path to the file specified in answer is
		 * tilde-expanded. */
1172
		answer = free_and_assign(answer, real_dir_from_tilde(answer));
1173

1174
		/* Save the file specified in answer in the current buffer. */
1175
		open_buffer(answer, TRUE);
1176
	    }
1177

1178
#ifdef ENABLE_MULTIBUFFER
1179
	    if (ISSET(MULTIBUFFER)) {
1180
1181
1182
#ifndef DISABLE_HISTORIES
		if (ISSET(POS_HISTORY)) {
		    ssize_t priorline, priorcol;
1183
#ifndef NANO_TINY
1184
		    if (!execute)
1185
#endif
1186
		    if (has_old_position(answer, &priorline, &priorcol))
1187
1188
1189
			do_gotolinecolumn(priorline, priorcol, FALSE, FALSE);
		}
#endif /* !DISABLE_HISTORIES */
1190
		/* Update stuff to account for the current buffer. */
1191
		prepare_for_display();
1192
	    } else
1193
#endif /* ENABLE_MULTIBUFFER */
1194
	    {
1195
1196
		/* Mark the file as modified if it changed. */
		if (openfile->current->lineno != was_current_lineno ||
1197
			openfile->current_x != was_current_x)
1198
		    set_modified();
1199

1200
		/* Update current_y to account for inserted lines. */
1201
		place_the_cursor(TRUE);
1202

1203
		refresh_needed = TRUE;
1204
	    }
1205

1206
1207
1208
	    break;
	}
    }
1209
1210

    free(given);
1211
1212
}

1213
/* If the current mode of operation allows it, go insert a file. */
1214
void do_insertfile_void(void)
1215
{
1216
    if (ISSET(RESTRICTED))
1217
	show_restricted_warning();
1218
#ifdef ENABLE_MULTIBUFFER
1219
    else if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
1220
	statusbar(_("Key invalid in non-multibuffer mode"));
1221
#endif
1222
    else
1223
	do_insertfile();
1224
1225
}

1226
/* When passed "[relative path]" or "[relative path][filename]" in
1227
 * origpath, return "[full path]" or "[full path][filename]" on success,
1228
1229
1230
1231
 * 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. */
1232
char *get_full_path(const char *origpath)
1233
{
1234
    int attempts = 0;
1235
	/* How often we've tried climbing back up the tree. */
1236
    struct stat fileinfo;
1237
    char *currentdir, *d_here, *d_there, *d_there_file = NULL;
1238
    char *last_slash;
1239
    bool path_only;
1240

1241
    if (origpath == NULL)
1242
	return NULL;
1243

1244
    /* Get the current directory.  If it doesn't exist, back up and try
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1245
1246
     * again until we get a directory that does, and use that as the
     * current directory. */
1247
1248
    currentdir = charalloc(PATH_MAX + 1);
    d_here = getcwd(currentdir, PATH_MAX + 1);
1249

1250
1251
    while (d_here == NULL && attempts < 20) {
	IGNORE_CALL_RESULT(chdir(".."));
Benno Schulenberg's avatar
Benno Schulenberg committed
1252
	d_here = getcwd(currentdir, PATH_MAX + 1);
1253
	attempts++;
1254
1255
1256
1257
    }

    /* If we succeeded, canonicalize it in d_here. */
    if (d_here != NULL) {
1258
1259
	/* If the current directory isn't "/", tack a slash onto the end
	 * of it. */
1260
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1261
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1262
	    strcat(d_here, "/");
1263
	}
1264
    /* Otherwise, set d_here to "". */
1265
    } else {
1266
	d_here = mallocstrcpy(NULL, "");
1267
1268
	free(currentdir);
    }
1269

1270
    d_there = real_dir_from_tilde(origpath);
1271

1272
1273
1274
    /* 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. */
1275
    path_only = (stat(d_there, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode));
1276

1277
1278
1279
    /* If path_only is TRUE, make sure d_there ends in a slash. */
    if (path_only) {
	size_t d_there_len = strlen(d_there);
1280

1281
1282
1283
	if (d_there[d_there_len - 1] != '/') {
	    d_there = charealloc(d_there, d_there_len + 2);
	    strcat(d_there, "/");
1284
	}
1285
    }
1286

1287
1288
    /* Search for the last slash in d_there. */
    last_slash = strrchr(d_there, '/');
1289

1290
1291
1292
1293
    /* 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);
1294

1295
1296
1297
1298
1299
1300
1301
1302
	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);

1303
	/* Remove the filename portion of the answer from d_there. */
1304
	*(last_slash + 1) = '\0';
1305

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1306
	/* Go to the path specified in d_there. */
1307
1308
1309
	if (chdir(d_there) == -1) {
	    free(d_there);
	    d_there = NULL;
1310
	} else {
1311
1312
1313
	    free(d_there);

	    /* Get the full path. */
1314
1315
	    currentdir = charalloc(PATH_MAX + 1);
	    d_there = getcwd(currentdir, PATH_MAX + 1);
1316
1317
1318
1319
1320
1321
1322
1323

	    /* If we succeeded, canonicalize it in d_there. */
	    if (d_there != NULL) {
		/* 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, "/");
1324
		}
1325
1326
	    /* Otherwise, make sure that we return NULL. */
	    } else {
1327
		path_only = TRUE;
1328
1329
		free(currentdir);
	    }
1330
1331
1332

	    /* Finally, go back to the path specified in d_here,
	     * where we were before.  We don't check for a chdir()
1333
	     * error, since we can do nothing if we get one. */
1334
	    IGNORE_CALL_RESULT(chdir(d_here));
1335
	}
1336
1337
1338

	/* Free d_here, since we're done using it. */
	free(d_here);
1339
    }
1340

1341
1342
1343
1344
1345
1346
1347
    /* 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) +
1348
		strlen(d_there_file) + 1);
1349
1350
	strcat(d_there, d_there_file);
    }
1351

1352
    /* Free d_there_file, since we're done using it. */
1353
    free(d_there_file);
1354

1355
    return d_there;
1356
}
1357

1358
/* Return the full version of path, as returned by get_full_path().  On
1359
 * error, or if path doesn't reference a directory, or if the directory
1360
 * isn't writable, return NULL. */
1361
char *check_writable_directory(const char *path)
1362
{
1363
1364
    char *full_path = get_full_path(path);

1365
    if (full_path == NULL)
1366
	return NULL;
1367

1368
    /* If we can't write to path or path isn't a directory, return NULL. */
1369
    if (access(full_path, W_OK) != 0 ||
1370
		full_path[strlen(full_path) - 1] != '/') {
1371
	free(full_path);
1372
	return NULL;
1373
    }
1374
1375
1376
1377

    return full_path;
}

1378
1379
/* This function calls mkstemp(($TMPDIR|P_tmpdir|/tmp/)"nano.XXXXXX").
 * On success, it returns the malloc()ed filename and corresponding FILE
1380
 * stream, opened in "r+b" mode.  On error, it returns NULL for the
1381
1382
 * filename and leaves the FILE stream unchanged. */
char *safe_tempfile(FILE **f)
1383
{
1384
    char *full_tempdir = NULL;
1385
1386
1387
    const char *tmpdir_env;
    int fd;
    mode_t original_umask = 0;
1388

1389
1390
    assert(f != NULL);

1391
1392
1393
    /* 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. */
1394
    tmpdir_env = getenv("TMPDIR");
1395
    if (tmpdir_env != NULL)
1396
	full_tempdir = check_writable_directory(tmpdir_env);
1397

1398
1399
    /* If $TMPDIR is unset, empty, or not a writable directory, and
     * full_tempdir is NULL, try P_tmpdir instead. */
1400
    if (full_tempdir == NULL)
1401
	full_tempdir = check_writable_directory(P_tmpdir);
1402

1403
1404
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
1405
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1406

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1407
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1408
    strcat(full_tempdir, "nano.XXXXXX");
1409

1410
1411
1412
1413
1414
1415
1416
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

    if (fd != -1)
	*f = fdopen(fd, "r+b");
1417
1418
1419
    else {
	free(full_tempdir);
	full_tempdir = NULL;
1420
    }
1421

1422
1423
    umask(original_umask);

1424
    return full_tempdir;
1425
}
1426
1427

#ifndef DISABLE_OPERATINGDIR
1428
1429
1430
1431
1432
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1433
    if (operating_dir == NULL)
1434
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1435

1436
1437
    full_operating_dir = get_full_path(operating_dir);

1438
1439
    /* If the operating directory is inaccessible, fail. */
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1)
1440
	die(_("Invalid operating directory\n"));
1441

1442
    snuggly_fit(&full_operating_dir);
1443
1444
}

1445
1446
1447
1448
1449
/* 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)
1450
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1451
1452
1453
1454
    /* 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. */
1455

1456
    char *fullpath;
1457
    bool retval = FALSE;
1458
    const char *whereami1, *whereami2 = NULL;
1459

1460
    /* If no operating directory is set, don't bother doing anything. */
1461
    if (operating_dir == NULL)
1462
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1463

1464
    assert(full_operating_dir != NULL);
1465
1466

    fullpath = get_full_path(currpath);
1467

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1468
1469
1470
    /* 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
1471
     * non-existent directory as being outside the operating directory,
1472
     * so we return FALSE.  If allow_tabcomp is TRUE, then currpath
1473
1474
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1475
    if (fullpath == NULL)
1476
	return allow_tabcomp;
1477
1478
1479
1480
1481

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

1482
    /* If both searches failed, we're outside the operating directory.
1483
     * Otherwise, check the search results.  If the full operating
1484
1485
1486
     * 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. */
1487
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1488
1489
	retval = TRUE;
    free(fullpath);
1490
1491

    /* Otherwise, we're still inside it. */
1492
    return retval;
1493
}
1494
1495
#endif

1496
#ifndef NANO_TINY
1497
1498
/* Although this sucks, it sucks less than having a single 'my system is
 * messed up and I'm blanket allowing insecure file writing operations'. */
1499
1500
int prompt_failed_backupwrite(const char *filename)
{
1501
    static int response;
1502
    static char *prevfile = NULL; /* What was the last file we were
1503
1504
				   * passed so we don't keep asking
				   * this?  Though maybe we should... */
1505
    if (prevfile == NULL || strcmp(filename, prevfile)) {
1506
1507
	response = do_yesno_prompt(FALSE, _("Failed to write backup file; "
			"continue saving? (Say N if unsure.) "));
1508
1509
	prevfile = mallocstrcpy(prevfile, filename);
    }
1510
1511

    return response;
1512
1513
}

1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
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 ||
1526
		full_backup_dir[strlen(full_backup_dir) - 1] != '/') {
1527
1528
1529
1530
1531
1532
	free(full_backup_dir);
	free(backup_dir);
	backup_dir = NULL;
    } else {
	free(backup_dir);
	backup_dir = full_backup_dir;
1533
	snuggly_fit(&backup_dir);
1534
1535
    }
}
1536
#endif /* !NANO_TINY */
1537

1538
/* Read from inn, write to out.  We assume inn is opened for reading,
1539
 * and out for writing.  We return 0 on success, -1 on read error, or -2
1540
1541
1542
 * on write error.  inn is always closed by this function, out is closed
 * only if close_out is true. */
int copy_file(FILE *inn, FILE *out, bool close_out)
1543
{
1544
    int retval = 0;
1545
    char buf[BUFSIZ];
1546
    size_t charsread;
1547
    int (*flush_out_fnc)(FILE *) = (close_out) ? fclose : fflush;
1548

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

1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
    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
1562

1563
1564
    if (fclose(inn) == EOF)
	retval = -1;
1565
    if (flush_out_fnc(out) == EOF)
1566
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1567

1568
1569
1570
    return retval;
}

1571
1572
/* 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
1573
 * ourselves.  If tmp is TRUE, we set the umask to disallow anyone else
1574
1575
 * 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
1576
 *
1577
 * tmp means we are writing a temporary file in a secure fashion.  We
1578
 * use it when spell checking or dumping the file on an error.  If
1579
1580
 * method is APPEND, it means we are appending instead of overwriting.
 * If method is PREPEND, it means we are prepending instead of
1581
 * overwriting.  If nonamechange is TRUE, we don't change the current
1582
1583
 * filename.  nonamechange is irrelevant when appending or prepending,
 * or when writing a temporary file.
1584
 *
1585
 * Return TRUE on success or FALSE on error. */
1586
1587
bool write_file(const char *name, FILE *f_open, bool tmp,
	kind_of_writing_type method, bool nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1588
{
1589
    bool retval = FALSE;
1590
	/* Instead of returning in this function, you should always
1591
	 * set retval and then goto cleanup_and_exit. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1592
    size_t lineswritten = 0;
1593
    const filestruct *fileptr = openfile->fileage;
1594
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1595
	/* The file descriptor we use. */
1596
    mode_t original_umask = 0;
1597
	/* Our umask, from when nano started. */
1598
#ifndef NANO_TINY
1599
    bool realexists;
1600
	/* The result of stat().  TRUE if the file exists, FALSE
1601
	 * otherwise.  If name is a link that points nowhere, realexists
1602
	 * is FALSE. */
1603
#endif
1604
1605
1606
    struct stat st;
	/* The status fields filled in by stat(). */
    char *realname;
1607
	/* The filename after tilde expansion. */
1608
    FILE *f = f_open;
1609
1610
	/* The actual file, realname, we are writing to. */
    char *tempname = NULL;
1611
	/* The name of the temporary file we write to on prepend. */
Chris Allegretta's avatar
Chris Allegretta committed
1612

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1613
    if (*name == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1614
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1615

1616
1617
    if (!tmp)
	titlebar(NULL);
1618

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1619
    realname = real_dir_from_tilde(name);
1620

1621
#ifndef DISABLE_OPERATINGDIR
1622
    /* If we're writing a temporary file, we're probably going outside
1623
     * the operating directory, so skip the operating directory test. */
1624
    if (!tmp && check_operating_dir(realname, FALSE)) {
1625
	statusline(ALERT, _("Can't write outside of %s"), full_operating_dir);
1626
	goto cleanup_and_exit;
1627
1628
1629
    }
#endif

1630
    /* If the temp file exists and isn't already open, give up. */
1631
    if (tmp && (lstat(realname, &st) != -1) && f_open == NULL)
1632
	goto cleanup_and_exit;
1633

1634
#ifndef NANO_TINY
1635
    /* Check whether the file (at the end of the symlink) exists. */
1636
    realexists = (stat(realname, &st) != -1);
1637

1638
1639
1640
1641
    /* If we haven't stat()d this file before (say, the user just
     * specified it interactively), stat and save the value now,
     * or else we will chase null pointers when we do modtime checks,
     * preserve file times, and so on, during backup. */
1642
1643
    if (openfile->current_stat == NULL && !tmp && realexists)
	stat_with_alloc(realname, &openfile->current_stat);
1644

1645
    /* We backup only if the backup toggle is set, the file isn't
1646
1647
1648
1649
     * 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. */
1650
    if (ISSET(BACKUP_FILE) && !tmp && realexists && openfile->current_stat &&
1651
		(method != OVERWRITE || openfile->mark_set ||
1652
		openfile->current_stat->st_mtime == st.st_mtime)) {
1653
	int backup_fd;
1654
	FILE *backup_file;
1655
	char *backupname;
1656
	static struct timespec filetime[2];
1657
	int backup_cflags;
1658

1659
	/* Save the original file's access and modification times. */
1660
1661
	filetime[0].tv_sec = openfile->current_stat->st_atime;
	filetime[1].tv_sec = openfile->current_stat->st_mtime;
1662

1663
1664
1665
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1666

1667
	    if (f == NULL) {
1668
		statusline(ALERT, _("Error reading %s: %s"), realname,
1669
			strerror(errno));
1670
1671
1672
1673
		/* 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;
1674
	    }
1675
1676
	}

1677
	/* If backup_dir is set, we set backupname to
1678
1679
1680
1681
	 * 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]. */
1682
	if (backup_dir != NULL) {
1683
	    char *backuptemp = get_full_path(realname);
1684

1685
	    if (backuptemp == NULL)
1686
1687
1688
1689
1690
1691
		/* 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~. */
1692
		backuptemp = mallocstrcpy(NULL, tail(realname));
1693
	    else {
1694
1695
		size_t i = 0;

1696
1697
1698
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1699
1700
1701
		}
	    }

1702
	    backupname = charalloc(strlen(backup_dir) + strlen(backuptemp) + 1);
1703
1704
1705
	    sprintf(backupname, "%s%s", backup_dir, backuptemp);
	    free(backuptemp);
	    backuptemp = get_next_filename(backupname, "~");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1706
	    if (*backuptemp == '\0') {
1707
1708
		statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, _("Too many backup files?"));
1709
1710
		free(backuptemp);
		free(backupname);
1711
1712
1713
1714
1715
		/* If we can't write to the backup, DON'T go on, since
		 * whatever caused the backup file to fail (e.g. disk
		 * full may well cause the real file write to fail,
		 * which means we could lose both the backup and the
		 * original! */
1716
		goto cleanup_and_exit;
1717
1718
1719
1720
	    } else {
		free(backupname);
		backupname = backuptemp;
	    }
1721
1722
1723
1724
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1725

1726
	/* First, unlink any existing backups.  Next, open the backup
1727
1728
	 * file with O_CREAT and O_EXCL.  If it succeeds, we have a file
	 * descriptor to a new backup file. */
1729
	if (unlink(backupname) < 0 && errno != ENOENT && !ISSET(INSECURE_BACKUP)) {
1730
1731
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1732
1733
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1734
1735
1736
1737
	    free(backupname);
	    goto cleanup_and_exit;
	}

1738
1739
	if (ISSET(INSECURE_BACKUP))
	    backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
1740
	else
1741
1742
1743
	    backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

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

1749
	if (backup_fd < 0 || backup_file == NULL) {
1750
1751
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1752
	    free(backupname);
1753
1754
1755
1756
	    /* If we can't make a backup, DON'T go on, since whatever caused
	     * the backup to fail (e.g. disk full) may well cause the real
	     * file write to fail, in which case we could lose both the
	     * backup and the original! */
1757
1758
	    goto cleanup_and_exit;
	}
1759

1760
1761
1762
1763
1764
1765
	/* Only try chowning the backup when we're root. */
	if (geteuid() == NANO_ROOT_UID &&
			fchown(backup_fd, openfile->current_stat->st_uid,
			openfile->current_stat->st_gid) == -1 &&
			!ISSET(INSECURE_BACKUP)) {
	    fclose(backup_file);
1766
1767
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1768
1769
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1770
1771
1772
1773
	    free(backupname);
	    goto cleanup_and_exit;
	}

1774
1775
1776
1777
	/* Set the backup's mode bits. */
	if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1 &&
			!ISSET(INSECURE_BACKUP)) {
	    fclose(backup_file);
1778
1779
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1780
1781
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1782
	    free(backupname);
1783
	    goto cleanup_and_exit;
1784
1785
1786
	}

#ifdef DEBUG
1787
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1788
1789
#endif

1790
	/* Copy the file. */
1791
1792
	if (copy_file(f, backup_file, FALSE) != 0) {
	    fclose(backup_file);
1793
	    statusline(ALERT, _("Error reading %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1794
			strerror(errno));
1795
1796
1797
	    goto cleanup_and_exit;
	}

1798
	/* And set the backup's timestamps. */
1799
1800
	if (futimens(backup_fd, filetime) == -1 && !ISSET(INSECURE_BACKUP)) {
	    fclose(backup_file);
1801
1802
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1803
1804
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1805
	    goto cleanup_and_exit;
1806
	}
1807

1808
	fclose(backup_file);
1809
1810
	free(backupname);
    }
1811

1812
    skip_backup:
1813
#endif /* !NANO_TINY */
1814

1815
1816
    if (f_open == NULL) {
	original_umask = umask(0);
1817

1818
	/* If we create a temp file, we don't let anyone else access it.
1819
1820
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1821
	    umask(S_IRWXG | S_IRWXO);
1822
1823
	else
	    umask(original_umask);
1824
    }
1825

1826
    /* If we're prepending, copy the file to a temp file. */
1827
    if (method == PREPEND) {
1828
1829
1830
	int fd_source;
	FILE *f_source = NULL;

1831
1832
1833
1834
	if (f == NULL) {
	    f = fopen(realname, "rb");

	    if (f == NULL) {
1835
		statusline(ALERT, _("Error reading %s: %s"), realname,
1836
1837
1838
1839
1840
			strerror(errno));
		goto cleanup_and_exit;
	    }
	}

1841
	tempname = safe_tempfile(&f);
1842

1843
	if (tempname == NULL) {
1844
	    statusline(ALERT, _("Error writing temp file: %s"),
1845
			strerror(errno));
1846
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1847
	}
1848

1849
	if (f_open == NULL) {
1850
	    fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1851

1852
1853
1854
	    if (fd_source != -1) {
		f_source = fdopen(fd_source, "rb");
		if (f_source == NULL) {
1855
		    statusline(ALERT, _("Error reading %s: %s"), realname,
1856
				strerror(errno));
1857
1858
1859
1860
1861
1862
		    close(fd_source);
		    fclose(f);
		    unlink(tempname);
		    goto cleanup_and_exit;
		}
	    }
1863
1864
	}

1865
	if (f_source == NULL || copy_file(f_source, f, TRUE) != 0) {
1866
	    statusline(ALERT, _("Error writing temp file: %s"),
1867
			strerror(errno));
1868
	    unlink(tempname);
1869
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1870
1871
1872
	}
    }

1873
1874
1875
    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*. */
1876
	fd = open(realname, O_WRONLY | O_CREAT | ((method == APPEND) ?
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1877
1878
		O_APPEND : (tmp ? O_EXCL : O_TRUNC)), S_IRUSR |
		S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1879

1880
1881
	/* Set the umask back to the user's original value. */
	umask(original_umask);
1882

1883
1884
	/* If we couldn't open the file, give up. */
	if (fd == -1) {
1885
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1886
			strerror(errno));
1887
1888
1889
1890
	    if (tempname != NULL)
		unlink(tempname);
	    goto cleanup_and_exit;
	}
1891

1892
	f = fdopen(fd, (method == APPEND) ? "ab" : "wb");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1893

1894
	if (f == NULL) {
1895
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1896
			strerror(errno));
1897
1898
1899
	    close(fd);
	    goto cleanup_and_exit;
	}
1900
1901
    }

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

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

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

1910
1911
	/* Convert nulls to newlines.  data_len is the string's real
	 * length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1912
1913
	unsunder(fileptr->data, data_len);

1914
	if (size < data_len) {
1915
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1916
			strerror(errno));
1917
	    fclose(f);
1918
	    goto cleanup_and_exit;
1919
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1920

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1921
	/* If we're on the last line of the file, don't write a newline
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1922
1923
1924
1925
1926
1927
1928
	 * 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 {
1929
#ifndef NANO_TINY
1930
	    if (openfile->fmt == DOS_FILE || openfile->fmt == MAC_FILE) {
1931
		if (putc('\r', f) == EOF) {
1932
		    statusline(ALERT, _("Error writing %s: %s"), realname,
1933
				strerror(errno));
1934
1935
1936
		    fclose(f);
		    goto cleanup_and_exit;
		}
1937
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1938

1939
	    if (openfile->fmt != MAC_FILE)
1940
#endif
1941
		if (putc('\n', f) == EOF) {
1942
		    statusline(ALERT, _("Error writing %s: %s"), realname,
1943
				strerror(errno));
1944
1945
1946
1947
		    fclose(f);
		    goto cleanup_and_exit;
		}
	}
Chris Allegretta's avatar
Chris Allegretta committed
1948
1949
1950
1951
1952

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

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

1958
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1959

1960
1961
1962
1963
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1964
	}
1965

1966
	if (f_source == NULL) {
1967
1968
	    statusline(ALERT, _("Error reading %s: %s"), tempname,
			strerror(errno));
1969
	    fclose(f);
1970
	    goto cleanup_and_exit;
1971
1972
	}

1973
	if (copy_file(f_source, f, TRUE) != 0) {
1974
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1975
			strerror(errno));
1976
	    goto cleanup_and_exit;
1977
	}
1978
1979

	unlink(tempname);
1980
    } else if (fclose(f) != 0) {
1981
	statusline(ALERT, _("Error writing %s: %s"), realname,
1982
			strerror(errno));
1983
	goto cleanup_and_exit;
1984
    }
1985

1986
    if (method == OVERWRITE && !tmp) {
1987
1988
	/* If we must set the filename, and it changed, adjust things. */
	if (!nonamechange && strcmp(openfile->filename, realname) != 0) {
1989
#ifndef DISABLE_COLOR
1990
	    char *oldname = openfile->syntax ? openfile->syntax->name : "";
1991
1992
1993
	    filestruct *line = openfile->fileage;
#endif
	    openfile->filename = mallocstrcpy(openfile->filename, realname);
1994

1995
1996
#ifndef DISABLE_COLOR
	    /* See if the applicable syntax has changed. */
1997
	    color_update();
1998
	    color_init();
1999

2000
2001
	    char *newname = openfile->syntax ? openfile->syntax->name : "";

2002
	    /* If the syntax changed, discard and recompute the multidata. */
2003
	    if (strcmp(oldname, newname) != 0) {
2004
2005
2006
2007
2008
		for (; line != NULL; line = line->next) {
		    free(line->multidata);
		    line->multidata = NULL;
		}
		precalc_multicolorinfo();
2009
		refresh_needed = TRUE;
2010
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2011
2012
#endif
	}
2013

2014
#ifndef NANO_TINY
2015
	if (!openfile->mark_set)
2016
2017
	    /* Get or update the stat info to reflect the current state. */
	    stat_with_alloc(realname, &openfile->current_stat);
2018
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2019

2020
2021
	statusline(HUSH, P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten), (unsigned long)lineswritten);
2022
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2023
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2024
    }
2025

2026
    retval = TRUE;
2027
2028
2029

  cleanup_and_exit:
    free(realname);
2030
    free(tempname);
2031

2032
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2033
2034
}

2035
#ifndef NANO_TINY
2036
2037
/* Write a marked selection from a file out to disk.  Return TRUE on
 * success or FALSE on error. */
2038
bool write_marked_file(const char *name, FILE *f_open, bool tmp,
2039
	kind_of_writing_type method)
2040
{
2041
    bool retval;
2042
    bool old_modified = openfile->modified;
2043
	/* Save the status, because write_file() unsets the modified flag. */
2044
    bool added_magicline = FALSE;
2045
2046
2047
2048
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

2049
    /* Partition the buffer so that it contains only the marked text. */
2050
    mark_order((const filestruct **)&top, &top_x,
2051
		(const filestruct **)&bot, &bot_x, NULL);
2052
    filepart = partition_filestruct(top, top_x, bot, bot_x);
2053

2054
2055
2056
    /* If we are doing magicline, and the last line of the partition
     * isn't blank, then add a newline at the end of the buffer. */
    if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0') {
2057
	new_magicline();
2058
2059
	added_magicline = TRUE;
    }
2060

2061
    retval = write_file(name, f_open, tmp, method, TRUE);
2062

2063
2064
    /* If we added a magicline, remove it now. */
    if (added_magicline)
2065
2066
	remove_magicline();

2067
    /* Unpartition the buffer so that it contains all the text again. */
2068
    unpartition_filestruct(&filepart);
2069

2070
    if (old_modified)
2071
2072
2073
2074
	set_modified();

    return retval;
}
2075

2076
#endif /* !NANO_TINY */
2077

2078
/* Write the current file to disk.  If the mark is on, write the current
2079
2080
2081
2082
2083
 * marked selection to disk.  If exiting is TRUE, write the entire file
 * to disk regardless of whether the mark is on, and without prompting if
 * the TEMP_FILE flag is set and the current file has a name.  Return 0
 * on error, 1 on success, and 2 when the buffer is to be discarded. */
int do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
2084
{
2085
    int i;
2086
    bool result = FALSE;
2087
    kind_of_writing_type method = OVERWRITE;
2088
2089
    char *given;
	/* The filename we offer, or what the user typed so far. */
2090
    bool maychange = (openfile->filename[0] == '\0');
2091
	/* Whether it's okay to save the file under a different name. */
2092
#ifndef DISABLE_EXTRA
2093
    static bool did_credits = FALSE;
2094
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2095

2096
    /* Display newlines in filenames as ^J. */
2097
2098
    as_an_at = FALSE;

2099
2100
2101
2102
    if (exiting && ISSET(TEMP_FILE) && openfile->filename[0] != '\0') {
	if (write_file(openfile->filename, NULL, FALSE, OVERWRITE, FALSE))
	    return 1;
	/* If writing the file failed, go on to prompt for a new name. */
Chris Allegretta's avatar
Chris Allegretta committed
2103
2104
    }

2105
    given = mallocstrcpy(NULL,
2106
#ifndef NANO_TINY
2107
	(openfile->mark_set && !exiting) ? "" :
2108
#endif
2109
	openfile->filename);
2110
2111
2112

    while (TRUE) {
	const char *msg;
2113
#ifndef NANO_TINY
2114
2115
	const char *formatstr, *backupstr;

2116
	formatstr = (openfile->fmt == DOS_FILE) ? _(" [DOS Format]") :
2117
			(openfile->fmt == MAC_FILE) ? _(" [Mac Format]") : "";
2118

2119
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2120

2121
	/* If we're using restricted mode, don't display the "Write
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2122
2123
2124
	 * Selection to File" prompt.  This function is disabled, since
	 * it allows reading from or writing to files not specified on
	 * the command line. */
2125
	if (openfile->mark_set && !exiting && !ISSET(RESTRICTED))
2126
	    /* TRANSLATORS: The next six strings are prompts. */
2127
2128
	    msg = (method == PREPEND) ? _("Prepend Selection to File") :
			(method == APPEND) ? _("Append Selection to File") :
2129
			_("Write Selection to File");
2130
2131
2132
	else if (method != OVERWRITE)
	    msg = (method == PREPEND) ? _("File Name to Prepend to") :
					_("File Name to Append to");
2133
	else
2134
#endif /* !NANO_TINY */
2135
	    msg = _("File Name to Write");
2136

2137
2138
	present_path = mallocstrcpy(present_path, "./");

2139
2140
	/* If we're using restricted mode, and the filename isn't blank,
	 * disable tab completion. */
2141
	i = do_prompt(!ISSET(RESTRICTED) || openfile->filename[0] == '\0',
2142
		TRUE, MWRITEFILE, given,
2143
#ifndef DISABLE_HISTORIES
2144
2145
		NULL,
#endif
2146
		edit_refresh, "%s%s%s", msg,
2147
2148
#ifndef NANO_TINY
		formatstr, backupstr
2149
#else
2150
		"", ""
2151
2152
2153
#endif
		);

2154
	if (i < 0) {
2155
	    statusbar(_("Cancelled"));
2156
2157
	    break;
	} else {
2158
2159
	    functionptrtype func = func_from_key(&i);

2160
2161
2162
2163
	    /* Upon request, abandon the buffer, if user is sure. */
	    if (func == discard_buffer) {
		if (openfile->modified)
		    i = do_yesno_prompt(FALSE,
2164
				_("Save modified buffer anyway? "));
2165
2166
2167
2168
		else
		    i = 0;

		if (i == 0) {
2169
		    free(given);
2170
		    return 2;	/* Yes, discard the buffer. */
2171
2172
		} else
		    continue;	/* Go back to the filename prompt. */
2173
2174
	    }

2175
	    given = mallocstrcpy(given, answer);
Chris Allegretta's avatar
Chris Allegretta committed
2176

2177
#ifdef ENABLE_BROWSER
2178
	    if (func == to_files_void) {
2179
		char *chosen = do_browse_from(answer);
2180

2181
		if (chosen == NULL)
2182
		    continue;
2183

2184
		/* We have a file now.  Indicate this. */
2185
		free(answer);
2186
		answer = chosen;
2187
	    } else
2188
#endif
2189
#ifndef NANO_TINY
2190
	    if (func == dos_format_void) {
2191
2192
		openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE :
			DOS_FILE;
2193
		continue;
2194
	    } else if (func == mac_format_void) {
2195
2196
		openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE :
			MAC_FILE;
2197
		continue;
2198
	    } else if (func == backup_file_void) {
2199
2200
		TOGGLE(BACKUP_FILE);
		continue;
2201
	    } else if (func == prepend_void) {
2202
		method = (method == PREPEND) ? OVERWRITE : PREPEND;
2203
		continue;
2204
	    } else if (func == append_void) {
2205
		method = (method == APPEND) ? OVERWRITE : APPEND;
2206
		continue;
2207
2208
2209
	    } else
#endif /* !NANO_TINY */
	    if (func == do_help_void) {
2210
		continue;
2211
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2212

Chris Allegretta's avatar
Chris Allegretta committed
2213
#ifdef DEBUG
2214
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
2215
#endif
2216

2217
#ifndef DISABLE_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2218
2219
2220
2221
2222
2223
2224
	    /* 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) &&
2225
			strcasecmp(answer, "zzy") == 0) {
2226
		do_credits();
2227
		did_credits = TRUE;
2228
2229
		break;
	    }
2230
#endif
2231

2232
	    if (method == OVERWRITE) {
2233
		bool name_exists, do_warning;
2234
		char *full_answer, *full_filename;
2235
2236
		struct stat st;

2237
2238
		full_answer = get_full_path(answer);
		full_filename = get_full_path(openfile->filename);
2239
2240
		name_exists = (stat((full_answer == NULL) ?
				answer : full_answer, &st) != -1);
2241
2242
2243
2244
		if (openfile->filename[0] == '\0')
		    do_warning = name_exists;
		else
		    do_warning = (strcmp((full_answer == NULL) ?
2245
2246
				answer : full_answer, (full_filename == NULL) ?
				openfile->filename : full_filename) != 0);
2247

2248
2249
		free(full_filename);
		free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2250

2251
		if (do_warning) {
2252
2253
2254
		    /* When in restricted mode, we aren't allowed to overwrite
		     * an existing file with the current buffer, nor to change
		     * the name of the current file if it already has one. */
2255
		    if (ISSET(RESTRICTED)) {
2256
			/* TRANSLATORS: Restricted mode forbids overwriting. */
2257
2258
			warn_and_shortly_pause(_("File exists -- "
					"cannot overwrite"));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2259
			continue;
2260
		    }
2261

2262
		    if (!maychange) {
2263
#ifndef NANO_TINY
2264
2265
2266
			if (exiting || !openfile->mark_set)
#endif
			{
2267
2268
			    if (do_yesno_prompt(FALSE, _("Save file under "
					"DIFFERENT NAME? ")) < 1)
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
				continue;
			    maychange = TRUE;
			}
		    }

		    if (name_exists) {
			char *question = _("File \"%s\" exists; OVERWRITE? ");
			char *message = charalloc(strlen(question) +
						strlen(answer) + 1);
			sprintf(message, question, answer);

			i = do_yesno_prompt(FALSE, message);
			free(message);

			if (i < 1)
2284
2285
			    continue;
		    }
2286
		}
2287
#ifndef NANO_TINY
2288
2289
2290
		/* Complain if the file exists, the name hasn't changed,
		 * and the stat information we had before does not match
		 * what we have now. */
2291
2292
2293
2294
		else if (name_exists && openfile->current_stat &&
			(openfile->current_stat->st_mtime < st.st_mtime ||
			openfile->current_stat->st_dev != st.st_dev ||
			openfile->current_stat->st_ino != st.st_ino)) {
2295
2296
2297

		    if (do_yesno_prompt(FALSE, _("File was modified since "
				"you opened it; continue saving? ")) < 1)
2298
2299
			continue;
		}
2300
#endif
2301
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2302

2303
	    /* Here's where we allow the selected text to be written to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2304
2305
2306
	     * 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. */
2307
#ifndef NANO_TINY
2308
	    if (openfile->mark_set && !exiting && !ISSET(RESTRICTED))
2309
		result = write_marked_file(answer, NULL, FALSE, method);
2310
	    else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2311
#endif
2312
		result = write_file(answer, NULL, FALSE, method, FALSE);
2313

2314
2315
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2316
    }
2317

2318
    free(given);
2319

2320
    return result ? 1 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
2321
2322
}

2323
/* Write the current buffer to disk, or discard it. */
2324
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
2325
{
2326
2327
2328
    /* If the user chose to discard the buffer, close it. */
    if (do_writeout(FALSE) == 2)
	close_and_go();
Chris Allegretta's avatar
Chris Allegretta committed
2329
}
Chris Allegretta's avatar
Chris Allegretta committed
2330

2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
#ifndef NANO_TINY
/* If it has a name, write the current file to disk without prompting. */
void do_savefile(void)
{
    if (openfile->filename[0] != '\0')
	write_file(openfile->filename, NULL, FALSE, OVERWRITE, FALSE);
    else
	do_writeout_void();
}
#endif

2342
2343
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
2344
char *real_dir_from_tilde(const char *buf)
2345
{
2346
    char *retval;
2347

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2348
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2349
	size_t i = 1;
2350
	char *tilde_dir;
2351

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

2356
2357
2358
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
2359
	    tilde_dir = mallocstrcpy(NULL, homedir);
2360
	} else {
2361
#ifdef HAVE_PWD_H
2362
2363
	    const struct passwd *userdata;

2364
2365
2366
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

2367
2368
	    do {
		userdata = getpwent();
2369
2370
	    } while (userdata != NULL &&
			strcmp(userdata->pw_name, tilde_dir + 1) != 0);
2371
	    endpwent();
2372
	    if (userdata != NULL)
2373
		tilde_dir = mallocstrcpy(tilde_dir, userdata->pw_dir);
2374
2375
2376
#else
	    tilde_dir = strdup("");
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2377
	}
2378

2379
2380
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2381

2382
2383
2384
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2385

2386
    return retval;
2387
2388
}

2389
#if defined(ENABLE_TABCOMP) || defined(ENABLE_BROWSER)
2390
/* Our sort routine for file listings.  Sort alphabetically and
2391
 * case-insensitively, and sort directories before filenames. */
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
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;

2405
2406
2407
    /* Standard function brain damage: We should be sorting
     * alphabetically and case-insensitively according to the current
     * locale, but there's no standard strcasecoll() function, so we
Benno Schulenberg's avatar
Benno Schulenberg committed
2408
     * have to use multibyte strcasecmp() instead. */
2409
    return mbstrcasecmp(a, b);
2410
}
2411
2412
2413
2414
2415

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

2419
2420
    for (; len > 0; len--)
	free(array[len - 1]);
2421

2422
2423
    free(array);
}
2424
2425
#endif

2426
#ifdef ENABLE_TABCOMP
2427
/* Is the given path a directory? */
2428
bool is_dir(const char *buf)
2429
{
2430
    char *dirptr;
2431
    struct stat fileinfo;
2432
2433
2434
2435
    bool retval;

    dirptr = real_dir_from_tilde(buf);

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

2438
    free(dirptr);
2439

2440
    return retval;
2441
}
Chris Allegretta's avatar
Chris Allegretta committed
2442

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2443
2444
2445
/* 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
2446
 * file, with the copyright years updated:
Chris Allegretta's avatar
Chris Allegretta committed
2447
2448
2449
 *
 * Termios command line History and Editting, originally
 * intended for NetBSD sh (ash)
2450
 * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
Chris Allegretta's avatar
Chris Allegretta committed
2451
2452
2453
2454
2455
2456
2457
2458
 *      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.
2459
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
2460

2461
/* We consider the first buf_len characters of buf for ~username tab
2462
2463
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
2464
	size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2465
{
2466
    char **matches = NULL;
2467

2468
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2469

2470
    *num_matches = 0;
2471

2472
2473
2474
#ifdef HAVE_PWD_H
    const struct passwd *userdata;

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

2480
2481
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2482
2483
2484
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2485
2486
#endif

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2487
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
2488
2489
					sizeof(char *));
	    matches[*num_matches] = charalloc(strlen(userdata->pw_name) + 2);
2490
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2491
	    ++(*num_matches);
2492
	}
2493
2494
    }
    endpwent();
2495
#endif
2496

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

2500
/* We consider the first buf_len characters of buf for filename tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2501
 * completion. */
2502
char **cwd_tab_completion(const char *buf, bool allow_files, size_t
2503
	*num_matches, size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2504
{
2505
2506
    char *dirname = mallocstrcpy(NULL, buf);
    char *slash, *filename;
2507
2508
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2509
    DIR *dir;
2510
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2511

2512
    *num_matches = 0;
2513
    dirname[buf_len] = '\0';
2514

2515
    /* If there's a / in the name, split out filename and directory parts. */
2516
2517
2518
    slash = strrchr(dirname, '/');
    if (slash != NULL) {
	char *wasdirname = dirname;
2519

2520
2521
	filename = mallocstrcpy(NULL, ++slash);
	/* Cut off the filename part after the slash. */
2522
	*slash = '\0';
2523
	dirname = real_dir_from_tilde(dirname);
2524
2525
2526
2527
2528
2529
	/* A non-absolute path is relative to the current browser directory. */
	if (dirname[0] != '/') {
	    dirname = charealloc(dirname, strlen(present_path) +
						strlen(wasdirname) + 1);
	    sprintf(dirname, "%s%s", present_path, wasdirname);
	}
2530
	free(wasdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2531
    } else {
2532
	filename = dirname;
2533
	dirname = mallocstrcpy(NULL, present_path);
Chris Allegretta's avatar
Chris Allegretta committed
2534
2535
    }

2536
    assert(dirname[strlen(dirname) - 1] == '/');
2537

Chris Allegretta's avatar
Chris Allegretta committed
2538
    dir = opendir(dirname);
2539

2540
    if (dir == NULL) {
2541
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2542
	beep();
2543
2544
2545
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2546
    }
2547
2548
2549

    filenamelen = strlen(filename);

2550
    while ((nextdir = readdir(dir)) != NULL) {
2551
2552
	bool skip_match = FALSE;

Chris Allegretta's avatar
Chris Allegretta committed
2553
#ifdef DEBUG
2554
	fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2555
#endif
2556
	/* See if this matches. */
2557
	if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
2558
2559
		(*filename == '.' || (strcmp(nextdir->d_name, ".") != 0 &&
		strcmp(nextdir->d_name, "..") != 0))) {
2560
2561
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2562

2563
	    char *tmp = charalloc(strlen(dirname) + strlen(nextdir->d_name) + 1);
2564
2565
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

2566
2567
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2568
2569
2570
2571
2572
2573
2574
	     * 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. */
2575
	    if (!allow_files && !is_dir(tmp))
2576
2577
2578
		skip_match = TRUE;

	    free(tmp);
2579

2580
	    if (skip_match)
2581
		continue;
2582

2583
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
2584
					sizeof(char *));
2585
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2586
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2587
2588
	}
    }
2589

2590
2591
    closedir(dir);
    free(dirname);
2592
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2593

2594
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2595
2596
}

2597
/* Do tab completion.  place refers to how much the statusbar cursor
2598
2599
 * position should be advanced.  refresh_func is the function we will
 * call to refresh the edit window. */
2600
char *input_tab(char *buf, bool allow_files, size_t *place,
2601
	bool *lastwastab, void (*refresh_func)(void), bool *listed)
Chris Allegretta's avatar
Chris Allegretta committed
2602
{
2603
    size_t num_matches = 0, buf_len;
2604
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2605

2606
    assert(buf != NULL && place != NULL && *place <= strlen(buf) &&
2607
2608
2609
		lastwastab != NULL && refresh_func != NULL && listed != NULL);

    *listed = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2610

2611
2612
    /* 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
2613
    if (*place > 0 && *buf == '~') {
2614
	const char *slash = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2615

2616
	if (slash == NULL || slash >= buf + *place)
2617
	    matches = username_tab_completion(buf, &num_matches, *place);
2618
    }
2619

2620
2621
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
2622
	matches = cwd_tab_completion(buf, allow_files, &num_matches, *place);
2623

2624
2625
2626
    buf_len = strlen(buf);

    if (num_matches == 0 || *place != buf_len)
2627
2628
2629
	beep();
    else {
	size_t match, common_len = 0;
2630
	char *mzero, *glued;
2631
	const char *lastslash = revstrstr(buf, "/", buf + *place);
2632
	size_t lastslash_len = (lastslash == NULL) ? 0 : lastslash - buf + 1;
2633
	char char1[MAXCHARLEN], char2[MAXCHARLEN];
2634
	int len1, len2;
2635

2636
	/* Get the number of characters that all matches have in common. */
2637
	while (TRUE) {
2638
	    len1 = parse_mbchar(matches[0] + common_len, char1, NULL);
2639
2640

	    for (match = 1; match < num_matches; match++) {
2641
2642
2643
		len2 = parse_mbchar(matches[match] + common_len, char2, NULL);

		if (len1 != len2 || strncmp(char1, char2, len2) != 0)
2644
2645
		    break;
	    }
2646

2647
	    if (match < num_matches || matches[0][common_len] == '\0')
2648
		break;
2649

2650
	    common_len += len1;
2651
	}
2652

2653
	mzero = charalloc(lastslash_len + common_len + 1);
2654
2655
2656

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

2658
	common_len += lastslash_len;
2659
	mzero[common_len] = '\0';
2660

2661
2662
2663
2664
	/* Cover also the case of the user specifying a relative path. */
	glued = charalloc(strlen(present_path) + strlen(mzero) + 1);
	sprintf(glued, "%s%s", present_path, mzero);

2665
	assert(common_len >= *place);
2666

2667
	if (num_matches == 1 && (is_dir(mzero) || is_dir(glued))) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2668
	    mzero[common_len++] = '/';
2669

2670
2671
	    assert(common_len > *place);
	}
2672

2673
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2674
	    beep();
2675

2676
	/* If the matches have something in common, show that part. */
2677
	if (common_len != *place) {
2678
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
2679
	    charmove(buf + common_len, buf + *place, buf_len - *place + 1);
2680
	    strncpy(buf, mzero, common_len);
2681
	    *place = common_len;
2682
2683
2684
	}

	if (!*lastwastab)
2685
	    *lastwastab = TRUE;
2686
	else if (num_matches > 1) {
2687
	    int longest_name = 0, ncols, editline = 0;
2688

2689
	    /* Sort the list of available choices. */
2690
2691
	    qsort(matches, num_matches, sizeof(char *), diralphasort);

2692
	    /* Find the length of the longest among the choices. */
2693
	    for (match = 0; match < num_matches; match++) {
2694
		size_t namelen = strlenpt(matches[match]);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2695

2696
2697
		if (namelen > longest_name)
		    longest_name = namelen;
2698
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2699

2700
2701
	    if (longest_name > COLS - 1)
		longest_name = COLS - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2702

2703
	    /* Each column will be (longest_name + 2) columns wide, i.e.
2704
2705
	     * two spaces between columns, except that there will be
	     * only one space after the last column. */
2706
	    ncols = (COLS + 1) / (longest_name + 2);
2707

2708
	    /* Blank the edit window and hide the cursor. */
2709
2710
	    blank_edit();
	    curs_set(0);
2711
	    wmove(edit, 0, 0);
2712

2713
	    /* Now print the list of matches out there. */
2714
2715
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2716

2717
		wmove(edit, editline, (longest_name + 2) * (match % ncols));
2718

2719
		if (match % ncols == 0 && editline == editwinrows - 1 &&
2720
			num_matches - match > ncols) {
2721
2722
2723
		    waddstr(edit, _("(more)"));
		    break;
		}
2724

2725
		disp = display_string(matches[match], 0, longest_name, FALSE);
2726
2727
		waddstr(edit, disp);
		free(disp);
2728

2729
		if ((match + 1) % ncols == 0)
2730
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2731
	    }
2732

2733
	    wnoutrefresh(edit);
2734
	    *listed = TRUE;
2735
2736
	}

2737
	free(glued);
2738
	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2739
2740
    }

2741
    free_chararray(matches, num_matches);
2742

2743
2744
2745
    /* When we didn't list any matches now, refresh the edit window, just
     * in case a previous tab showed a list, so we know where we are. */
    if (!*listed)
2746
2747
	refresh_func();

2748
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2749
}
2750
#endif /* ENABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2751

2752
2753
/* Return the filename part of the given path. */
const char *tail(const char *path)
2754
{
2755
    const char *slash = strrchr(path, '/');
2756

2757
2758
    if (slash == NULL)
	return path;
2759
    else
2760
	return ++slash;
2761
2762
}

2763
2764
#ifndef DISABLE_HISTORIES
/* Return the constructed dirfile path, or NULL if we can't find the home
2765
 * directory.  The string is dynamically allocated, and should be freed. */
2766
char *construct_filename(const char *str)
2767
{
2768
    char *newstr = NULL;
2769

2770
2771
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2772

2773
2774
2775
	newstr = charalloc(homelen + strlen(str) + 1);
	strcpy(newstr, homedir);
	strcpy(newstr + homelen, str);
Chris Allegretta's avatar
Chris Allegretta committed
2776
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2777

2778
2779
2780
2781
2782
    return newstr;
}

char *histfilename(void)
{
2783
    return construct_filename("/.nano/search_history");
2784
2785
}

2786
2787
/* Construct the legacy history filename. */
/* (To be removed in 2018.) */
2788
2789
2790
2791
2792
2793
2794
char *legacyhistfilename(void)
{
    return construct_filename("/.nano_history");
}

char *poshistfilename(void)
{
2795
    return construct_filename("/.nano/filepos_history");
2796
2797
2798
2799
}

void history_error(const char *msg, ...)
{
2800
    va_list ap;
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810

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

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

2811
2812
2813
/* 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. */
2814
2815
int check_dotnano(void)
{
2816
    int ret = 1;
2817
2818
2819
2820
2821
    struct stat dirstat;
    char *nanodir = construct_filename("/.nano");

    if (stat(nanodir, &dirstat) == -1) {
	if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
2822
	    history_error(N_("Unable to create directory %s: %s\n"
2823
2824
				"It is required for saving/loading "
				"search history or cursor positions.\n"),
2825
				nanodir, strerror(errno));
2826
	    ret = 0;
2827
2828
	}
    } else if (!S_ISDIR(dirstat.st_mode)) {
2829
	history_error(N_("Path %s is not a directory and needs to be.\n"
2830
2831
				"Nano will be unable to load or save "
				"search history or cursor positions.\n"),
2832
				nanodir);
2833
	ret = 0;
2834
    }
2835
2836
2837

    free(nanodir);
    return ret;
2838
2839
}

2840
/* Load the search and replace histories from ~/.nano/search_history. */
2841
2842
2843
void load_history(void)
{
    char *nanohist = histfilename();
2844
2845
2846
    char *legacyhist = legacyhistfilename();
    struct stat hstat;

2847
2848
    /* If there is an old history file, migrate it. */
    /* (To be removed in 2018.) */
2849
    if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
2850
	if (rename(legacyhist, nanohist) == -1)
2851
2852
2853
	    history_error(N_("Detected a legacy nano history file (%s) which I tried to move\n"
			     "to the preferred location (%s) but encountered an error: %s"),
				legacyhist, nanohist, strerror(errno));
2854
	else
2855
2856
2857
	    history_error(N_("Detected a legacy nano history file (%s) which I moved\n"
			     "to the preferred location (%s)\n(see the nano FAQ about this change)"),
				legacyhist, nanohist);
2858
2859
    }

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

2864
	if (hist == NULL) {
2865
	    if (errno != ENOENT) {
2866
2867
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2868
		history_error(N_("Error reading %s: %s"), nanohist,
2869
			strerror(errno));
2870
	    }
2871
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2872
2873
2874
	    /* 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. */
2875
	    filestruct **history = &search_history;
2876
	    char *line = NULL;
2877
	    size_t buf_len = 0;
2878
2879
	    ssize_t read;

2880
2881
	    while ((read = getline(&line, &buf_len, hist)) > 0) {
		line[--read] = '\0';
2882
2883
2884
		if (read > 0) {
		    /* Encode any embedded NUL as 0x0A. */
		    unsunder(line, read);
2885
		    update_history(history, line);
2886
		} else
2887
2888
		    history = &replace_history;
	    }
2889

2890
	    fclose(hist);
2891
	    free(line);
2892
	}
2893
	free(nanohist);
2894
	free(legacyhist);
2895
2896
2897
    }
}

2898
/* Write the lines of a history list, starting with the line at head, to
2899
2900
 * the open file at hist.  Return TRUE if the write succeeded, and FALSE
 * otherwise. */
2901
bool writehist(FILE *hist, const filestruct *head)
2902
{
2903
    const filestruct *item;
2904

2905
    /* Write a history list, from the oldest item to the newest. */
2906
2907
    for (item = head; item != NULL; item = item->next) {
	size_t length = strlen(item->data);
2908

2909
2910
2911
	/* Decode 0x0A bytes as embedded NULs. */
	sunder(item->data);

2912
2913
2914
	if (fwrite(item->data, sizeof(char), length, hist) < length)
	    return FALSE;
	if (putc('\n', hist) == EOF)
2915
	    return FALSE;
2916
    }
2917

2918
    return TRUE;
2919
2920
}

2921
/* Save the search and replace histories to ~/.nano/search_history. */
2922
2923
void save_history(void)
{
2924
    char *nanohist;
2925

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2926
    /* Don't save unchanged or empty histories. */
2927
    if (!history_has_changed() || (searchbot->lineno == 1 &&
2928
		replacebot->lineno == 1))
2929
2930
	return;

2931
2932
2933
2934
    nanohist = histfilename();

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

2936
	if (hist == NULL)
2937
2938
	    fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
			strerror(errno));
2939
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2940
2941
	    /* Make sure no one else can read from or write to the
	     * history file. */
2942
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2943

2944
	    if (!writehist(hist, searchage) || !writehist(hist, replaceage))
2945
		fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
2946
			strerror(errno));
2947

2948
2949
	    fclose(hist);
	}
2950

2951
2952
2953
	free(nanohist);
    }
}
2954

2955
/* Save the recorded last file positions to ~/.nano/filepos_history. */
2956
2957
void save_poshistory(void)
{
2958
    char *poshist = poshistfilename();
2959
    poshiststruct *posptr;
2960
    FILE *hist;
2961

2962
2963
    if (poshist == NULL)
	return;
2964

2965
    hist = fopen(poshist, "wb");
2966

2967
2968
2969
2970
2971
    if (hist == NULL)
	fprintf(stderr, _("Error writing %s: %s\n"), poshist, strerror(errno));
    else {
	/* Don't allow others to read or write the history file. */
	chmod(poshist, S_IRUSR | S_IWUSR);
2972

2973
	for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2974
	    char *path_and_place;
2975
2976
	    size_t length;

2977
2978
	    /* Assume 20 decimal positions each for line and column number,
	     * plus two spaces, plus the line feed, plus the null byte. */
2979
2980
	    path_and_place = charalloc(strlen(posptr->filename) + 44);
	    sprintf(path_and_place, "%s %ld %ld\n", posptr->filename,
2981
			(long)posptr->lineno, (long)posptr->xno);
2982
	    length = strlen(path_and_place);
2983
2984

	    /* Encode newlines in filenames as nulls. */
2985
	    sunder(path_and_place);
2986
	    /* Restore the terminating newline. */
2987
	    path_and_place[length - 1] = '\n';
2988

2989
	    if (fwrite(path_and_place, sizeof(char), length, hist) < length)
2990
2991
		fprintf(stderr, _("Error writing %s: %s\n"),
					poshist, strerror(errno));
2992
	    free(path_and_place);
2993
	}
2994
	fclose(hist);
2995
    }
2996
    free(poshist);
2997
2998
}

2999
3000
/* Update the recorded last file positions, given a filename, a line
 * and a column.  If no entry is found, add a new one at the end. */
3001
3002
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
3003
    poshiststruct *posptr, *theone, *posprev = NULL;
3004
    char *fullpath = get_full_path(filename);
3005

3006
3007
    if (fullpath == NULL || fullpath[strlen(fullpath) - 1] == '/') {
	free(fullpath);
3008
	return;
3009
    }
3010

3011
    /* Look for a matching filename in the list. */
3012
    for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
3013
3014
	if (!strcmp(posptr->filename, fullpath))
	    break;
3015
3016
3017
	posprev = posptr;
    }

3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
    /* Don't record files that have the default cursor position. */
    if (lineno == 1 && xpos == 1) {
	if (posptr != NULL) {
	    if (posprev == NULL)
		position_history = posptr->next;
	    else
		posprev->next = posptr->next;
	    free(posptr->filename);
	    free(posptr);
	}
	free(fullpath);
	return;
    }

3032
    theone = posptr;
3033

3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
    /* If we didn't find it, make a new node; otherwise, if we're
     * not at the end, move the matching one to the end. */
    if (theone == NULL) {
	theone = (poshiststruct *)nmalloc(sizeof(poshiststruct));
	theone->filename = mallocstrcpy(NULL, fullpath);
	if (position_history == NULL)
	    position_history = theone;
	else
	    posprev->next = theone;
    } else if (posptr->next != NULL) {
3044
3045
3046
3047
	if (posprev == NULL)
	    position_history = posptr->next;
	else
	    posprev->next = posptr->next;
3048
3049
3050
3051
3052
3053
3054
3055
3056
	while (posptr->next != NULL)
	    posptr = posptr->next;
	posptr->next = theone;
    }

    /* Store the last cursor position. */
    theone->lineno = lineno;
    theone->xno = xpos;
    theone->next = NULL;
3057
3058
3059
3060

    free(fullpath);
}

3061
3062
3063
3064
/* Check whether the given file matches an existing entry in the recorded
 * last file positions.  If not, return FALSE.  If yes, return TRUE and
 * set line and column to the retrieved values. */
bool has_old_position(const char *file, ssize_t *line, ssize_t *column)
3065
{
3066
    poshiststruct *posptr = position_history;
3067
3068
3069
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
3070
3071
3072
3073
	return FALSE;

    while (posptr != NULL && strcmp(posptr->filename, fullpath) != 0)
	posptr = posptr->next;
3074

3075
    free(fullpath);
3076
3077

    if (posptr == NULL)
3078
	return FALSE;
3079
3080
3081
3082

    *line = posptr->lineno;
    *column = posptr->xno;
    return TRUE;
3083
3084
}

3085
/* Load the recorded file positions from ~/.nano/filepos_history. */
3086
3087
void load_poshistory(void)
{
3088
    char *poshist = poshistfilename();
3089
    FILE *hist;
3090

3091
3092
3093
    /* If the home directory is missing, do_rcfile() will have reported it. */
    if (poshist == NULL)
	return;
3094

3095
    hist = fopen(poshist, "rb");
3096

3097
3098
    if (hist == NULL) {
	if (errno != ENOENT) {
3099
	    /* When reading failed, don't save history when we quit. */
3100
3101
	    UNSET(POS_HISTORY);
	    history_error(N_("Error reading %s: %s"), poshist, strerror(errno));
3102
	}
3103
3104
3105
    } else {
	char *line = NULL, *lineptr, *xptr;
	size_t buf_len = 0;
3106
	ssize_t read, count = 0;
3107
3108
3109
	poshiststruct *record_ptr = NULL, *newrecord;

	/* Read and parse each line, and store the extracted data. */
3110
3111
	while ((read = getline(&line, &buf_len, hist)) > 5) {
	    /* Decode nulls as embedded newlines. */
3112
3113
	    unsunder(line, read);

3114
3115
	    /* Find where the x index and line number are in the line. */
	    xptr = revstrstr(line, " ", line + read - 3);
3116
3117
	    if (xptr == NULL)
		continue;
3118
	    lineptr = revstrstr(line, " ", xptr - 2);
3119
3120
	    if (lineptr == NULL)
		continue;
3121
3122
3123
3124

	    /* Now separate the three elements of the line. */
	    *(xptr++) = '\0';
	    *(lineptr++) = '\0';
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139

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

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

	    record_ptr = newrecord;
3140
3141

	    /* Impose a limit, so the file will not grow indefinitely. */
3142
3143
3144
	    if (++count > 200) {
		poshiststruct *drop_record = position_history;

3145
		position_history = position_history->next;
3146
3147
3148
3149

		free(drop_record->filename);
		free(drop_record);
	    }
3150
3151
3152
	}
	fclose(hist);
	free(line);
3153
    }
3154
    free(poshist);
3155
}
3156
#endif /* !DISABLE_HISTORIES */