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 (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
#ifndef DISABLE_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, FALSE, &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
/* Update the screen to account for the current buffer. */
585
void display_buffer(void)
586
{
587
    /* Update the titlebar, since the filename may have changed. */
588
    titlebar(NULL);
589

590
#ifndef DISABLE_COLOR
591
    /* Make sure we're using the buffer's associated colors. */
592
    color_init();
593
594
595
596
597
598

    /* 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();
599
600
#endif

601
602
    /* Update the content of the edit window straightaway. */
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
603
604
}

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

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

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

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

625
626
627
628
629
630
631
632
#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

633
    /* Update the screen to account for the current buffer. */
634
    display_buffer();
635

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

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

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

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

658
659
660
661
/* 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)
662
{
663
    assert(openfile != NULL);
664

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

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

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

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

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

685
    return TRUE;
686
}
687
#endif /* !DISABLE_MULTIBUFFER */
688

689
690
691
692
/* 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. */
693
694
695
696
697
698
int is_file_writable(const char *filename)
{
    struct stat fileinfo, fileinfo2;
    int fd;
    FILE *f;
    char *full_filename;
699
    bool result = TRUE;
700
701
702
703

    if (ISSET(VIEW_MODE))
	return TRUE;

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

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

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

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

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

    return result;
726
727
}

728
729
730
/* 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)
731
{
732
    unsunder(buf, buf_len);
733
    buf[buf_len] = '\0';
734

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

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

774
    buf = charalloc(bufx);
775

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

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

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

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

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

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

#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
854
855
856
857
858
859
    }

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

865
866
867
868
869
870
871
    /* 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;

872
#ifndef NANO_TINY
873
874
875
876
877
878
	/* 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;
879

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

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

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

898
    free(buf);
899

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

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

906
    if (!writable)
907
	statusline(ALERT, _("File '%s' is unwritable"), filename);
908
#ifndef NANO_TINY
909
    else if (format == 3) {
910
911
912
913
	/* 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);
914
    } else if (format == 2) {
915
	openfile->fmt = MAC_FILE;
916
917
918
	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);
919
    } else if (format == 1) {
920
	openfile->fmt = DOS_FILE;
921
922
923
	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);
924
    }
925
#endif
926
    else
927
928
	statusline(HUSH, P_("Read %lu line", "Read %lu lines",
			(unsigned long)num_lines), (unsigned long)num_lines);
929

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

934
#ifndef NANO_TINY
935
936
937
    if (undoable)
	update_undo(INSERT);

938
939
    if (ISSET(MAKE_IT_UNIX))
	openfile->fmt = NIX_FILE;
940
#endif
941
}
Chris Allegretta's avatar
Chris Allegretta committed
942

943
944
/* 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.
945
 * Return -2 if we say "New File", -1 if the file isn't opened, and the
946
 * obtained fd otherwise.  *f is set to the opened file. */
947
int open_file(const char *filename, bool newfie, bool quiet, FILE **f)
948
{
949
    struct stat fileinfo, fileinfo2;
950
    int fd;
951
    char *full_filename;
952

953
    assert(filename != NULL && f != NULL);
954

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

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

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

972
	statusline(ALERT, _("File \"%s\" not found"), filename);
973
	free(full_filename);
974
975
	return -1;
    }
976

977
978
979
    /* 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)) {
980
	statusline(ALERT, S_ISDIR(fileinfo.st_mode) ?
981
982
			_("\"%s\" is a directory") :
			_("\"%s\" is a device file"), filename);
983
	free(full_filename);
984
	return -1;
985
986
987
988
989
990
991
992
993
    }

    /* 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. */
994
	*f = fdopen(fd, "rb");
995

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

1003
1004
    free(full_filename);

1005
1006
1007
    return fd;
}

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

1018
    assert(name != NULL && suffix != NULL);
1019

1020
    wholenamelen = strlen(name) + strlen(suffix);
1021

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

1027
1028
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
1029

1030
1031
	if (stat(buf, &fs) == -1)
	    return buf;
1032
1033
1034

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

1037
	sprintf(buf + wholenamelen, ".%lu", i);
1038
    }
Chris Allegretta's avatar
Chris Allegretta committed
1039

1040
1041
    /* There is no possible save file: blank out the filename. */
    *buf = '\0';
1042

1043
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
1044
1045
}

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

1058
    /* Display newlines in filenames as ^J. */
1059
1060
    as_an_at = FALSE;

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

1082
1083
	present_path = mallocstrcpy(present_path, "./");

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

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

1111
#ifndef NANO_TINY
1112
#ifndef DISABLE_MULTIBUFFER
1113
	    if (func == new_buffer_void) {
1114
		/* Don't allow toggling when in view mode. */
1115
1116
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
1117
1118
		else
		    beep();
1119
		continue;
1120
	    }
1121
#endif
1122
	    if (func == flip_execute_void) {
1123
1124
1125
		execute = !execute;
		continue;
	    }
1126
#endif /* !NANO_TINY */
1127

1128
#ifndef DISABLE_BROWSER
1129
	    if (func == to_files_void) {
1130
		char *chosen = do_browse_from(answer);
1131

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

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

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

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

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

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

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

1198
1199
		/* Update current_y to account for inserted lines. */
		place_the_cursor();
1200

1201
		refresh_needed = TRUE;
1202
	    }
1203

1204
1205
1206
	    break;
	}
    }
1207
1208

    free(given);
1209
1210
}

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

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

1239
    if (origpath == NULL)
1240
	return NULL;
1241

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

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

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

1268
    d_there = real_dir_from_tilde(origpath);
1269

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

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

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

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

1288
1289
1290
1291
    /* 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);
1292

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

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

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

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

	    /* 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, "/");
1322
		}
1323
1324
	    /* Otherwise, make sure that we return NULL. */
	    } else {
1325
		path_only = TRUE;
1326
1327
		free(currentdir);
	    }
1328
1329
1330

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

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

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

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

1353
    return d_there;
1354
}
1355

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

1363
    if (full_path == NULL)
1364
	return NULL;
1365

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

    return full_path;
}

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

1387
1388
    assert(f != NULL);

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

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

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

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

1408
1409
1410
1411
1412
1413
1414
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

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

1420
1421
    umask(original_umask);

1422
    return full_tempdir;
1423
}
1424
1425

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

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

1434
1435
    full_operating_dir = get_full_path(operating_dir);

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

1440
    snuggly_fit(&full_operating_dir);
1441
1442
}

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

1454
    char *fullpath;
1455
    bool retval = FALSE;
1456
    const char *whereami1, *whereami2 = NULL;
1457

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

1462
    assert(full_operating_dir != NULL);
1463
1464

    fullpath = get_full_path(currpath);
1465

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

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

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

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

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

    return response;
1510
1511
}

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

1536
/* Read from inn, write to out.  We assume inn is opened for reading,
1537
 * and out for writing.  We return 0 on success, -1 on read error, or -2
1538
1539
1540
 * 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)
1541
{
1542
    int retval = 0;
1543
    char buf[BUFSIZ];
1544
    size_t charsread;
1545
    int (*flush_out_fnc)(FILE *) = (close_out) ? fclose : fflush;
1546

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

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

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

1566
1567
1568
    return retval;
}

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

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

1614
1615
    if (!tmp)
	titlebar(NULL);
1616

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1617
    realname = real_dir_from_tilde(name);
1618

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

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

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

1636
1637
1638
1639
    /* 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. */
1640
1641
    if (openfile->current_stat == NULL && !tmp && realexists)
	stat_with_alloc(realname, &openfile->current_stat);
1642

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

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

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

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

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

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

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

1700
	    backupname = charalloc(strlen(backup_dir) + strlen(backuptemp) + 1);
1701
1702
1703
	    sprintf(backupname, "%s%s", backup_dir, backuptemp);
	    free(backuptemp);
	    backuptemp = get_next_filename(backupname, "~");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1704
	    if (*backuptemp == '\0') {
1705
1706
		statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, _("Too many backup files?"));
1707
1708
		free(backuptemp);
		free(backupname);
1709
1710
1711
1712
1713
		/* 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! */
1714
		goto cleanup_and_exit;
1715
1716
1717
1718
	    } else {
		free(backupname);
		backupname = backuptemp;
	    }
1719
1720
1721
1722
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1723

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

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

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

1747
	if (backup_fd < 0 || backup_file == NULL) {
1748
1749
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1750
	    free(backupname);
1751
1752
1753
1754
	    /* 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! */
1755
1756
	    goto cleanup_and_exit;
	}
1757

1758
1759
1760
1761
1762
1763
	/* 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);
1764
1765
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1766
1767
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1768
1769
1770
1771
	    free(backupname);
	    goto cleanup_and_exit;
	}

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

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

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

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

1806
	fclose(backup_file);
1807
1808
	free(backupname);
    }
1809

1810
    skip_backup:
1811
#endif /* !NANO_TINY */
1812

1813
1814
    if (f_open == NULL) {
	original_umask = umask(0);
1815

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

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

1829
1830
1831
1832
	if (f == NULL) {
	    f = fopen(realname, "rb");

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

1839
	tempname = safe_tempfile(&f);
1840

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1956
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1957

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

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

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

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

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

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

1998
1999
	    char *newname = openfile->syntax ? openfile->syntax->name : "";

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

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

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

2024
    retval = TRUE;
2025
2026
2027

  cleanup_and_exit:
    free(realname);
2028
    free(tempname);
2029

2030
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2031
2032
}

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

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

2052
2053
2054
    /* 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') {
2055
	new_magicline();
2056
2057
	added_magicline = TRUE;
    }
2058

2059
    retval = write_file(name, f_open, tmp, method, TRUE);
2060

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

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

2068
    if (old_modified)
2069
2070
2071
2072
	set_modified();

    return retval;
}
2073

2074
#endif /* !NANO_TINY */
2075

2076
/* Write the current file to disk.  If the mark is on, write the current
2077
2078
2079
2080
2081
 * 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
2082
{
2083
    int i;
2084
    bool result = FALSE;
2085
    kind_of_writing_type method = OVERWRITE;
2086
2087
    char *given;
	/* The filename we offer, or what the user typed so far. */
2088
    bool maychange = (openfile->filename[0] == '\0');
2089
	/* Whether it's okay to save the file under a different name. */
2090
#ifndef DISABLE_EXTRA
2091
    static bool did_credits = FALSE;
2092
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2093

2094
    /* Display newlines in filenames as ^J. */
2095
2096
    as_an_at = FALSE;

2097
2098
2099
2100
    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
2101
2102
    }

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

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

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

2117
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2118

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

2134
2135
	present_path = mallocstrcpy(present_path, "./");

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

2151
	if (i < 0) {
2152
	    statusbar(_("Cancelled"));
2153
2154
	    break;
	} else {
2155
2156
	    functionptrtype func = func_from_key(&i);

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

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

2172
	    given = mallocstrcpy(given, answer);
Chris Allegretta's avatar
Chris Allegretta committed
2173

2174
#ifndef DISABLE_BROWSER
2175
	    if (func == to_files_void) {
2176
		char *chosen = do_browse_from(answer);
2177

2178
		if (chosen == NULL)
2179
		    continue;
2180

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

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

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

2229
	    if (method == OVERWRITE) {
2230
		bool name_exists, do_warning;
2231
		char *full_answer, *full_filename;
2232
2233
		struct stat st;

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

2245
2246
		free(full_filename);
		free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2247

2248
		if (do_warning) {
2249
2250
2251
		    /* 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. */
2252
		    if (ISSET(RESTRICTED)) {
2253
			/* TRANSLATORS: Restricted mode forbids overwriting. */
2254
2255
			warn_and_shortly_pause(_("File exists -- "
					"cannot overwrite"));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2256
			continue;
2257
		    }
2258

2259
		    if (!maychange) {
2260
#ifndef NANO_TINY
2261
2262
2263
			if (exiting || !openfile->mark_set)
#endif
			{
2264
2265
			    if (do_yesno_prompt(FALSE, _("Save file under "
					"DIFFERENT NAME? ")) < 1)
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
				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)
2281
2282
			    continue;
		    }
2283
		}
2284
#ifndef NANO_TINY
2285
2286
2287
		/* Complain if the file exists, the name hasn't changed,
		 * and the stat information we had before does not match
		 * what we have now. */
2288
2289
2290
2291
		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)) {
2292
2293
2294

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

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

2311
2312
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2313
    }
2314

2315
    free(given);
2316

2317
    return result ? 1 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
2318
2319
}

2320
/* Write the current buffer to disk, or discard it. */
2321
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
2322
{
2323
2324
2325
    /* 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
2326
}
Chris Allegretta's avatar
Chris Allegretta committed
2327

2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
#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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2345
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2346
	size_t i = 1;
2347
	char *tilde_dir;
2348

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

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

2361
2362
2363
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

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

2376
2377
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2378

2379
2380
2381
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2382

2383
    return retval;
2384
2385
}

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

2402
2403
2404
    /* 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
2405
     * have to use multibyte strcasecmp() instead. */
2406
    return mbstrcasecmp(a, b);
2407
}
2408
2409
2410
2411
2412

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

2416
2417
    for (; len > 0; len--)
	free(array[len - 1]);
2418

2419
2420
    free(array);
}
2421
2422
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2423
#ifndef DISABLE_TABCOMP
2424
/* Is the given path a directory? */
2425
bool is_dir(const char *buf)
2426
{
2427
    char *dirptr;
2428
    struct stat fileinfo;
2429
2430
2431
2432
    bool retval;

    dirptr = real_dir_from_tilde(buf);

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

2435
    free(dirptr);
2436

2437
    return retval;
2438
}
Chris Allegretta's avatar
Chris Allegretta committed
2439

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

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

2465
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2466

2467
    *num_matches = 0;
2468

2469
2470
2471
#ifdef HAVE_PWD_H
    const struct passwd *userdata;

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

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

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

2494
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2495
2496
}

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

2509
    *num_matches = 0;
2510
    dirname[buf_len] = '\0';
2511

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

2517
2518
	filename = mallocstrcpy(NULL, ++slash);
	/* Cut off the filename part after the slash. */
2519
	*slash = '\0';
2520
	dirname = real_dir_from_tilde(dirname);
2521
2522
2523
2524
2525
2526
	/* 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);
	}
2527
	free(wasdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2528
    } else {
2529
	filename = dirname;
2530
	dirname = mallocstrcpy(NULL, present_path);
Chris Allegretta's avatar
Chris Allegretta committed
2531
2532
    }

2533
    assert(dirname[strlen(dirname) - 1] == '/');
2534

Chris Allegretta's avatar
Chris Allegretta committed
2535
    dir = opendir(dirname);
2536

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

    filenamelen = strlen(filename);

2547
    while ((nextdir = readdir(dir)) != NULL) {
2548
2549
	bool skip_match = FALSE;

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

2560
	    char *tmp = charalloc(strlen(dirname) + strlen(nextdir->d_name) + 1);
2561
2562
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

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

	    free(tmp);
2576

2577
	    if (skip_match)
2578
		continue;
2579

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

2587
2588
    closedir(dir);
    free(dirname);
2589
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2590

2591
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2592
2593
}

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

2603
    assert(buf != NULL && place != NULL && *place <= strlen(buf) &&
2604
2605
2606
		lastwastab != NULL && refresh_func != NULL && listed != NULL);

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

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

2613
	if (slash == NULL || slash >= buf + *place)
2614
	    matches = username_tab_completion(buf, &num_matches, *place);
2615
    }
2616

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

2621
2622
2623
    buf_len = strlen(buf);

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

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

	    for (match = 1; match < num_matches; match++) {
2638
2639
2640
		len2 = parse_mbchar(matches[match] + common_len, char2, NULL);

		if (len1 != len2 || strncmp(char1, char2, len2) != 0)
2641
2642
		    break;
	    }
2643

2644
	    if (match < num_matches || matches[0][common_len] == '\0')
2645
		break;
2646

2647
	    common_len += len1;
2648
	}
2649

2650
	mzero = charalloc(lastslash_len + common_len + 1);
2651
2652
2653

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

2655
	common_len += lastslash_len;
2656
	mzero[common_len] = '\0';
2657

2658
2659
2660
2661
	/* 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);

2662
	assert(common_len >= *place);
2663

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

2667
2668
	    assert(common_len > *place);
	}
2669

2670
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2671
	    beep();
2672

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

	if (!*lastwastab)
2682
	    *lastwastab = TRUE;
2683
	else if (num_matches > 1) {
2684
	    int longest_name = 0, ncols, editline = 0;
2685

2686
	    /* Sort the list of available choices. */
2687
2688
	    qsort(matches, num_matches, sizeof(char *), diralphasort);

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

2693
2694
		if (namelen > longest_name)
		    longest_name = namelen;
2695
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2696

2697
2698
	    if (longest_name > COLS - 1)
		longest_name = COLS - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2699

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

2705
	    /* Blank the edit window and hide the cursor. */
2706
2707
	    blank_edit();
	    curs_set(0);
2708
	    wmove(edit, 0, 0);
2709

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

2714
		wmove(edit, editline, (longest_name + 2) * (match % ncols));
2715

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

2722
		disp = display_string(matches[match], 0, longest_name, FALSE);
2723
2724
		waddstr(edit, disp);
		free(disp);
2725

2726
		if ((match + 1) % ncols == 0)
2727
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2728
	    }
2729

2730
	    wnoutrefresh(edit);
2731
	    *listed = TRUE;
2732
2733
	}

2734
	free(glued);
2735
	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2736
2737
    }

2738
    free_chararray(matches, num_matches);
2739

2740
2741
2742
    /* 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)
2743
2744
	refresh_func();

2745
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2746
}
2747
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2748

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

2754
2755
    if (slash == NULL)
	return path;
2756
    else
2757
	return ++slash;
2758
2759
}

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

2767
2768
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2769

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

2775
2776
2777
2778
2779
    return newstr;
}

char *histfilename(void)
{
2780
    return construct_filename("/.nano/search_history");
2781
2782
}

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

char *poshistfilename(void)
{
2792
    return construct_filename("/.nano/filepos_history");
2793
2794
2795
2796
}

void history_error(const char *msg, ...)
{
2797
    va_list ap;
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807

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

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

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

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

    free(nanodir);
    return ret;
2835
2836
}

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

2844
2845
    /* If there is an old history file, migrate it. */
    /* (To be removed in 2018.) */
2846
    if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
2847
	if (rename(legacyhist, nanohist) == -1)
2848
2849
2850
	    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));
2851
	else
2852
2853
2854
	    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);
2855
2856
    }

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

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

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

2887
	    fclose(hist);
2888
	    free(line);
2889
	}
2890
	free(nanohist);
2891
	free(legacyhist);
2892
2893
2894
    }
}

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

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

2906
2907
2908
	/* Decode 0x0A bytes as embedded NULs. */
	sunder(item->data);

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

2915
    return TRUE;
2916
2917
}

2918
/* Save the search and replace histories to ~/.nano/search_history. */
2919
2920
void save_history(void)
{
2921
    char *nanohist;
2922

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

2928
2929
2930
2931
    nanohist = histfilename();

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

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

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

2945
2946
	    fclose(hist);
	}
2947

2948
2949
2950
	free(nanohist);
    }
}
2951

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

2959
2960
    if (poshist == NULL)
	return;
2961

2962
    hist = fopen(poshist, "wb");
2963

2964
2965
2966
2967
2968
    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);
2969

2970
	for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2971
	    char *path_and_place;
2972
2973
	    size_t length;

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

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

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

2996
2997
/* 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. */
2998
2999
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
3000
    poshiststruct *posptr, *theone, *posprev = NULL;
3001
    char *fullpath = get_full_path(filename);
3002

3003
3004
    if (fullpath == NULL || fullpath[strlen(fullpath) - 1] == '/') {
	free(fullpath);
3005
	return;
3006
    }
3007

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

3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
    /* 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;
    }

3029
    theone = posptr;
3030

3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
    /* 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) {
3041
3042
3043
3044
	if (posprev == NULL)
	    position_history = posptr->next;
	else
	    posprev->next = posptr->next;
3045
3046
3047
3048
3049
3050
3051
3052
3053
	while (posptr->next != NULL)
	    posptr = posptr->next;
	posptr->next = theone;
    }

    /* Store the last cursor position. */
    theone->lineno = lineno;
    theone->xno = xpos;
    theone->next = NULL;
3054
3055
3056
3057

    free(fullpath);
}

3058
3059
3060
3061
/* 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)
3062
{
3063
    poshiststruct *posptr = position_history;
3064
3065
3066
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
3067
3068
3069
3070
	return FALSE;

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

3072
    free(fullpath);
3073
3074
3075
3076
3077
3078
3079

    if (posptr == NULL)
        return FALSE;

    *line = posptr->lineno;
    *column = posptr->xno;
    return TRUE;
3080
3081
}

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

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

3092
    hist = fopen(poshist, "rb");
3093

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

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

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

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

	    /* 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;
3137
3138

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

3142
		position_history = position_history->next;
3143
3144
3145
3146

		free(drop_record->filename);
		free(drop_record);
	    }
3147
3148
3149
	}
	fclose(hist);
	free(line);
3150
    }
3151
    free(poshist);
3152
}
3153
#endif /* !DISABLE_HISTORIES */