files.c 90 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

24
#include <stdarg.h>
Chris Allegretta's avatar
Chris Allegretta committed
25
#include <stdio.h>
26
#include <string.h>
Chris Allegretta's avatar
Chris Allegretta committed
27
28
29
#include <unistd.h>
#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
435
    /* Display newlines in filenames as ^J. */
    as_an_at = FALSE;

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

444
445
    realname = real_dir_from_tilde(filename);

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

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

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

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

482
483
484
    /* 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)) ?
485
		open_file(realname, new_buffer, inhelp, &f) : -2;
486

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

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

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

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

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

528
    /* Open the file quietly. */
529
530
531
532
533
    descriptor = open_file(filename, FALSE, TRUE, &f);

    /* If opening failed, forget it. */
    if (descriptor < 0)
	return;
534
535
536
537
538

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

539
540
    /* Insert the processed file into its place. */
    read_file(f, descriptor, filename, FALSE, TRUE);
541
542
543

    /* Put current at a place that is certain to exist. */
    openfile->current = openfile->fileage;
544
}
545
546
547
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

#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);
}
577
#endif /* !NANO_TINY */
578
579
#endif /* !DISABLE_SPELLER */

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

587
#ifndef DISABLE_COLOR
588
589
590
591
592
    /* 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();
593

594
595
    have_palette = FALSE;
#endif
596
    refresh_needed = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
597
598
}

599
#ifdef ENABLE_MULTIBUFFER
600
601
/* Switch to a neighbouring file buffer; to the next if to_next is TRUE;
 * otherwise, to the previous one. */
602
void switch_to_prevnext_buffer(bool to_next)
Chris Allegretta's avatar
Chris Allegretta committed
603
{
604
    /* If only one file buffer is open, say so and get out. */
605
    if (openfile == openfile->next && !inhelp) {
606
	statusbar(_("No more open file buffers"));
607
	return;
Chris Allegretta's avatar
Chris Allegretta committed
608
    }
609

610
611
    /* Switch to the next or previous file buffer. */
    openfile = to_next ? openfile->next : openfile->prev;
612
613

#ifdef DEBUG
614
    fprintf(stderr, "filename is %s\n", openfile->filename);
615
616
#endif

617
618
619
620
621
622
623
624
#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

625
626
    /* Update titlebar and multiline info to match the current buffer. */
    prepare_for_display();
627

628
629
630
631
632
633
    if (inhelp)
	return;

    /* Ensure that the main loop will redraw the help lines. */
    currmenu = MMOST;

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

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

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

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

656
657
658
659
/* 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)
660
{
661
    /* If only one file buffer is open, get out. */
662
    if (openfile == openfile->next)
663
	return FALSE;
664

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

671
    /* Switch to the next file buffer. */
672
    switch_to_prevnext_buffer(TRUE);
673

674
    /* Close the file buffer we had open before. */
675
    unlink_opennode(openfile->prev);
676

677
    /* If now just one buffer remains open, show "Exit" in the help lines. */
678
679
    if (openfile == openfile->next)
	exitfunc->desc = exit_tag;
680

681
    return TRUE;
682
}
683
#endif /* ENABLE_MULTIBUFFER */
684

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

    if (ISSET(VIEW_MODE))
	return TRUE;

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

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

    if ((fd = open(full_filename, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR |
710
711
		S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) == -1 ||
		(f = fdopen(fd, "a")) == NULL)
712
	result = FALSE;
713
    else
714
	fclose(f);
715

716
    close(fd);
717
    free(full_filename);
718
719

    return result;
720
721
}

722
723
724
/* 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)
725
{
726
    unsunder(buf, buf_len);
727
    buf[buf_len] = '\0';
728

729
    return mallocstrcpy(NULL, buf);
730
}
731

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

768
    buf = charalloc(bufx);
769

770
771
772
#ifndef NANO_TINY
    if (undoable)
	add_undo(INSERT);
773
774

    if (ISSET(SOFTWRAP))
775
	was_leftedge = leftedge_for(xplustabs(), openfile->current);
776
777
#endif

778
779
780
781
782
    /* Create an empty buffer. */
    topline = make_new_node(NULL);
    bottomline = topline;

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

814
815
	    /* Keep track of the total length of the line.  It might have
	     * nulls in it, so we can't just use strlen() later. */
816
	    len++;
817

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

#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
848
849
850
851
852
853
    }

    /* Perhaps this could use some better handling. */
    if (ferror(f))
	nperror(filename);
    fclose(f);
854
    if (fd > 0 && checkwritable) {
855
	close(fd);
856
857
	writable = is_file_writable(filename);
    }
858

859
860
861
862
863
864
865
    /* 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;

866
#ifndef NANO_TINY
867
868
869
870
871
872
	/* 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;
873

874
875
	    /* Strip the carriage return. */
	    buf[--len] = '\0';
876

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

885
886
887
888
	if (mac_line_needs_newline) {
	    bottomline->next = make_new_node(bottomline);
	    bottomline = bottomline->next;
	    bottomline->data = mallocstrcpy(NULL, "");
889
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
890
    }
891

892
    free(buf);
893

894
895
    /* Insert the just read buffer into the current one. */
    ingraft_buffer(topline);
896

897
    /* Set the desired x position at the end of what was inserted. */
898
899
    openfile->placewewant = xplustabs();

900
901
902
903
    /* If we've read a help file, don't give any feedback. */
    if (inhelp)
	return;

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

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

932
#ifndef NANO_TINY
933
934
935
    if (undoable)
	update_undo(INSERT);

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

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

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

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

    if (stat(full_filename, &fileinfo) == -1) {
961
	if (newfie) {
962
	    if (!quiet)
963
		statusbar(_("New File"));
964
	    free(full_filename);
965
966
	    return -2;
	}
967

968
	statusline(ALERT, _("File \"%s\" not found"), filename);
969
	free(full_filename);
970
971
	return -1;
    }
972

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

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

992
	if (*f == NULL) {
993
	    statusline(ALERT, _("Error reading %s: %s"), filename, strerror(errno));
994
	    close(fd);
995
	} else if (!inhelp)
996
	    statusbar(_("Reading File"));
997
    }
998

999
1000
    free(full_filename);

1001
1002
1003
    return fd;
}

1004
1005
1006
1007
1008
/* 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)
1009
{
1010
1011
    unsigned long i = 0;
    char *buf;
1012
    size_t wholenamelen;
1013

1014
    wholenamelen = strlen(name) + strlen(suffix);
1015

1016
1017
1018
    /* Reserve space for: the name plus the suffix plus a dot plus
     * possibly five digits plus a null byte. */
    buf = charalloc(wholenamelen + 7);
1019
    sprintf(buf, "%s%s", name, suffix);
1020

1021
1022
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
1023

1024
1025
	if (stat(buf, &fs) == -1)
	    return buf;
1026
1027
1028

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

1031
	sprintf(buf + wholenamelen, ".%lu", i);
1032
    }
Chris Allegretta's avatar
Chris Allegretta committed
1033

1034
1035
    /* There is no possible save file: blank out the filename. */
    *buf = '\0';
1036

1037
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
1038
1039
}

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

1052
    /* Display newlines in filenames as ^J. */
1053
1054
    as_an_at = FALSE;

1055
    while (TRUE) {
1056
#ifndef NANO_TINY
1057
	if (execute) {
1058
#ifdef ENABLE_MULTIBUFFER
1059
	    if (ISSET(MULTIBUFFER))
1060
		/* TRANSLATORS: The next four messages are prompts. */
1061
		msg = _("Command to execute in new buffer");
1062
	    else
1063
#endif
1064
		msg = _("Command to execute");
1065
1066
1067
	} else
#endif /* NANO_TINY */
	{
1068
#ifdef ENABLE_MULTIBUFFER
1069
	    if (ISSET(MULTIBUFFER))
1070
1071
1072
1073
		msg = _("File to insert into new buffer [from %s]");
	    else
#endif
		msg = _("File to insert [from %s]");
1074
	}
1075

1076
1077
	present_path = mallocstrcpy(present_path, "./");

1078
	i = do_prompt(TRUE, TRUE,
1079
#ifndef NANO_TINY
1080
		execute ? MEXTCMD :
1081
#endif
1082
		MINSERTFILE, given,
1083
#ifndef DISABLE_HISTORIES
1084
		NULL,
1085
#endif
1086
		edit_refresh, msg,
1087
#ifndef DISABLE_OPERATINGDIR
1088
		operating_dir != NULL ? operating_dir :
1089
#endif
1090
		"./");
1091

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

1105
#ifdef ENABLE_MULTIBUFFER
1106
	    if (func == flip_newbuffer) {
1107
		/* Don't allow toggling when in view mode. */
1108
1109
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
1110
1111
		else
		    beep();
1112
		continue;
1113
	    }
1114
#endif
1115
#ifndef NANO_TINY
1116
	    if (func == flip_execute) {
1117
1118
1119
		execute = !execute;
		continue;
	    }
1120
#endif
1121
#ifdef ENABLE_BROWSER
1122
	    if (func == to_files_void) {
1123
		char *chosen = do_browse_from(answer);
1124

1125
		/* If no file was chosen, go back to the prompt. */
1126
		if (chosen == NULL)
1127
		    continue;
1128

1129
		free(answer);
1130
		answer = chosen;
1131
1132
1133
		i = 0;
	    }
#endif
1134
1135
	    /* If we don't have a file yet, go back to the prompt. */
	    if (i != 0 && (!ISSET(MULTIBUFFER) || i != -2))
1136
		continue;
1137

1138
#ifndef NANO_TINY
1139
	    if (execute) {
1140
#ifdef ENABLE_MULTIBUFFER
1141
		/* When in multibuffer mode, first open a blank buffer. */
1142
		if (ISSET(MULTIBUFFER))
1143
		    open_buffer("", FALSE);
1144
1145
#endif
		/* Save the command's output in the current buffer. */
1146
		execute_command(answer);
1147

1148
#ifdef ENABLE_MULTIBUFFER
1149
		/* If this is a new buffer, put the cursor at the top. */
1150
1151
1152
1153
		if (ISSET(MULTIBUFFER)) {
		    openfile->current = openfile->fileage;
		    openfile->current_x = 0;
		    openfile->placewewant = 0;
1154
1155

		    set_modified();
1156
		}
1157
#endif
1158
	    } else
1159
#endif /* !NANO_TINY */
1160
	    {
1161
1162
		/* Make sure the path to the file specified in answer is
		 * tilde-expanded. */
1163
		answer = free_and_assign(answer, real_dir_from_tilde(answer));
1164

1165
		/* Save the file specified in answer in the current buffer. */
1166
		open_buffer(answer, TRUE);
1167
	    }
1168

1169
#ifdef ENABLE_MULTIBUFFER
1170
	    if (ISSET(MULTIBUFFER)) {
1171
1172
1173
#ifndef DISABLE_HISTORIES
		if (ISSET(POS_HISTORY)) {
		    ssize_t priorline, priorcol;
1174
#ifndef NANO_TINY
1175
		    if (!execute)
1176
#endif
1177
		    if (has_old_position(answer, &priorline, &priorcol))
1178
1179
1180
			do_gotolinecolumn(priorline, priorcol, FALSE, FALSE);
		}
#endif /* !DISABLE_HISTORIES */
1181
		/* Update stuff to account for the current buffer. */
1182
		prepare_for_display();
1183
	    } else
1184
#endif /* ENABLE_MULTIBUFFER */
1185
	    {
1186
1187
		/* Mark the file as modified if it changed. */
		if (openfile->current->lineno != was_current_lineno ||
1188
			openfile->current_x != was_current_x)
1189
		    set_modified();
1190

1191
		/* Update current_y to account for inserted lines. */
1192
		place_the_cursor(TRUE);
1193

1194
		refresh_needed = TRUE;
1195
	    }
1196

1197
1198
1199
	    break;
	}
    }
1200
1201

    free(given);
1202
1203
}

1204
/* If the current mode of operation allows it, go insert a file. */
1205
void do_insertfile_void(void)
1206
{
1207
    if (ISSET(RESTRICTED))
1208
	show_restricted_warning();
1209
#ifdef ENABLE_MULTIBUFFER
1210
    else if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
1211
	statusbar(_("Key invalid in non-multibuffer mode"));
1212
#endif
1213
    else
1214
	do_insertfile();
1215
1216
}

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

1232
    if (origpath == NULL)
1233
	return NULL;
1234

1235
    /* Get the current directory.  If it doesn't exist, back up and try
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1236
1237
     * again until we get a directory that does, and use that as the
     * current directory. */
1238
1239
    currentdir = charalloc(PATH_MAX + 1);
    d_here = getcwd(currentdir, PATH_MAX + 1);
1240

1241
1242
    while (d_here == NULL && attempts < 20) {
	IGNORE_CALL_RESULT(chdir(".."));
Benno Schulenberg's avatar
Benno Schulenberg committed
1243
	d_here = getcwd(currentdir, PATH_MAX + 1);
1244
	attempts++;
1245
1246
1247
1248
    }

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

1261
    d_there = real_dir_from_tilde(origpath);
1262

1263
1264
1265
    /* 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. */
1266
    path_only = (stat(d_there, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode));
1267

1268
1269
1270
    /* If path_only is TRUE, make sure d_there ends in a slash. */
    if (path_only) {
	size_t d_there_len = strlen(d_there);
1271

1272
1273
1274
	if (d_there[d_there_len - 1] != '/') {
	    d_there = charealloc(d_there, d_there_len + 2);
	    strcat(d_there, "/");
1275
	}
1276
    }
1277

1278
1279
    /* Search for the last slash in d_there. */
    last_slash = strrchr(d_there, '/');
1280

1281
1282
1283
1284
    /* 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);
1285

1286
1287
1288
1289
1290
1291
1292
1293
	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);

1294
	/* Remove the filename portion of the answer from d_there. */
1295
	*(last_slash + 1) = '\0';
1296

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1297
	/* Go to the path specified in d_there. */
1298
1299
1300
	if (chdir(d_there) == -1) {
	    free(d_there);
	    d_there = NULL;
1301
	} else {
1302
1303
1304
	    free(d_there);

	    /* Get the full path. */
1305
1306
	    currentdir = charalloc(PATH_MAX + 1);
	    d_there = getcwd(currentdir, PATH_MAX + 1);
1307
1308
1309
1310
1311
1312
1313
1314

	    /* 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, "/");
1315
		}
1316
1317
	    /* Otherwise, make sure that we return NULL. */
	    } else {
1318
		path_only = TRUE;
1319
1320
		free(currentdir);
	    }
1321
1322
1323

	    /* Finally, go back to the path specified in d_here,
	     * where we were before.  We don't check for a chdir()
1324
	     * error, since we can do nothing if we get one. */
1325
	    IGNORE_CALL_RESULT(chdir(d_here));
1326
	}
1327
1328
1329

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

1332
1333
1334
1335
1336
1337
1338
    /* 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) +
1339
		strlen(d_there_file) + 1);
1340
1341
	strcat(d_there, d_there_file);
    }
1342

1343
    /* Free d_there_file, since we're done using it. */
1344
    free(d_there_file);
1345

1346
    return d_there;
1347
}
1348

1349
/* Return the full version of path, as returned by get_full_path().  On
1350
 * error, or if path doesn't reference a directory, or if the directory
1351
 * isn't writable, return NULL. */
1352
char *check_writable_directory(const char *path)
1353
{
1354
1355
    char *full_path = get_full_path(path);

1356
    if (full_path == NULL)
1357
	return NULL;
1358

1359
    /* If we can't write to path or path isn't a directory, return NULL. */
1360
    if (access(full_path, W_OK) != 0 ||
1361
		full_path[strlen(full_path) - 1] != '/') {
1362
	free(full_path);
1363
	return NULL;
1364
    }
1365
1366
1367
1368

    return full_path;
}

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

1380
1381
1382
    /* 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. */
1383
    tmpdir_env = getenv("TMPDIR");
1384
    if (tmpdir_env != NULL)
1385
	full_tempdir = check_writable_directory(tmpdir_env);
1386

1387
1388
    /* If $TMPDIR is unset, empty, or not a writable directory, and
     * full_tempdir is NULL, try P_tmpdir instead. */
1389
    if (full_tempdir == NULL)
1390
	full_tempdir = check_writable_directory(P_tmpdir);
1391

1392
1393
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
1394
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1395

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1396
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1397
    strcat(full_tempdir, "nano.XXXXXX");
1398

1399
1400
1401
1402
1403
1404
1405
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

    if (fd != -1)
	*f = fdopen(fd, "r+b");
1406
1407
1408
    else {
	free(full_tempdir);
	full_tempdir = NULL;
1409
    }
1410

1411
1412
    umask(original_umask);

1413
    return full_tempdir;
1414
}
1415
1416

#ifndef DISABLE_OPERATINGDIR
1417
1418
1419
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
1420
    if (operating_dir == NULL)
1421
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1422

1423
1424
    full_operating_dir = get_full_path(operating_dir);

1425
1426
    /* If the operating directory is inaccessible, fail. */
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1)
1427
	die(_("Invalid operating directory\n"));
1428

1429
    snuggly_fit(&full_operating_dir);
1430
1431
}

1432
1433
1434
1435
1436
/* 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)
1437
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1438
1439
1440
1441
    /* 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. */
1442

1443
    char *fullpath;
1444
    bool retval = FALSE;
1445
    const char *whereami1, *whereami2 = NULL;
1446

1447
    /* If no operating directory is set, don't bother doing anything. */
1448
    if (operating_dir == NULL)
1449
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1450

1451
    fullpath = get_full_path(currpath);
1452

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1453
1454
1455
    /* 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
1456
     * non-existent directory as being outside the operating directory,
1457
     * so we return FALSE.  If allow_tabcomp is TRUE, then currpath
1458
1459
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1460
    if (fullpath == NULL)
1461
	return allow_tabcomp;
1462
1463
1464
1465
1466

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

1467
    /* If both searches failed, we're outside the operating directory.
1468
     * Otherwise, check the search results.  If the full operating
1469
1470
1471
     * 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. */
1472
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1473
1474
	retval = TRUE;
    free(fullpath);
1475
1476

    /* Otherwise, we're still inside it. */
1477
    return retval;
1478
}
1479
1480
#endif

1481
#ifndef NANO_TINY
1482
1483
/* Although this sucks, it sucks less than having a single 'my system is
 * messed up and I'm blanket allowing insecure file writing operations'. */
1484
1485
int prompt_failed_backupwrite(const char *filename)
{
1486
    static int response;
1487
    static char *prevfile = NULL; /* What was the last file we were
1488
1489
				   * passed so we don't keep asking
				   * this?  Though maybe we should... */
1490
    if (prevfile == NULL || strcmp(filename, prevfile)) {
1491
1492
	response = do_yesno_prompt(FALSE, _("Failed to write backup file; "
			"continue saving? (Say N if unsure.) "));
1493
1494
	prevfile = mallocstrcpy(prevfile, filename);
    }
1495
1496

    return response;
1497
1498
}

1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
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 ||
1511
		full_backup_dir[strlen(full_backup_dir) - 1] != '/') {
1512
1513
1514
1515
1516
1517
	free(full_backup_dir);
	free(backup_dir);
	backup_dir = NULL;
    } else {
	free(backup_dir);
	backup_dir = full_backup_dir;
1518
	snuggly_fit(&backup_dir);
1519
1520
    }
}
1521
#endif /* !NANO_TINY */
1522

1523
/* Read from inn, write to out.  We assume inn is opened for reading,
1524
 * and out for writing.  We return 0 on success, -1 on read error, or -2
1525
1526
1527
 * 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)
1528
{
1529
    int retval = 0;
1530
    char buf[BUFSIZ];
1531
    size_t charsread;
1532
    int (*flush_out_fnc)(FILE *) = (close_out) ? fclose : fflush;
1533

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

1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
    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
1547

1548
1549
    if (fclose(inn) == EOF)
	retval = -1;
1550
    if (flush_out_fnc(out) == EOF)
1551
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1552

1553
1554
1555
    return retval;
}

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1598
    if (*name == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1599
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1600

1601
1602
    if (!tmp)
	titlebar(NULL);
1603

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1604
    realname = real_dir_from_tilde(name);
1605

1606
#ifndef DISABLE_OPERATINGDIR
1607
    /* If we're writing a temporary file, we're probably going outside
1608
     * the operating directory, so skip the operating directory test. */
1609
    if (!tmp && check_operating_dir(realname, FALSE)) {
1610
	statusline(ALERT, _("Can't write outside of %s"), full_operating_dir);
1611
	goto cleanup_and_exit;
1612
1613
1614
    }
#endif

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

1619
#ifndef NANO_TINY
1620
    /* Check whether the file (at the end of the symlink) exists. */
1621
    realexists = (stat(realname, &st) != -1);
1622

1623
1624
1625
1626
    /* 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. */
1627
1628
    if (openfile->current_stat == NULL && !tmp && realexists)
	stat_with_alloc(realname, &openfile->current_stat);
1629

1630
    /* We backup only if the backup toggle is set, the file isn't
1631
1632
1633
1634
     * 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. */
1635
    if (ISSET(BACKUP_FILE) && !tmp && realexists && openfile->current_stat &&
1636
		(method != OVERWRITE || openfile->mark_set ||
1637
		openfile->current_stat->st_mtime == st.st_mtime)) {
1638
	int backup_fd;
1639
	FILE *backup_file;
1640
	char *backupname;
1641
	static struct timespec filetime[2];
1642
	int backup_cflags;
1643

1644
	/* Save the original file's access and modification times. */
1645
1646
	filetime[0].tv_sec = openfile->current_stat->st_atime;
	filetime[1].tv_sec = openfile->current_stat->st_mtime;
1647

1648
1649
1650
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1651

1652
	    if (f == NULL) {
1653
		statusline(ALERT, _("Error reading %s: %s"), realname,
1654
			strerror(errno));
1655
1656
1657
1658
		/* 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;
1659
	    }
1660
1661
	}

1662
	/* If backup_dir is set, we set backupname to
1663
1664
1665
1666
	 * 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]. */
1667
	if (backup_dir != NULL) {
1668
	    char *backuptemp = get_full_path(realname);
1669

1670
	    if (backuptemp == NULL)
1671
1672
1673
1674
1675
1676
		/* 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~. */
1677
		backuptemp = mallocstrcpy(NULL, tail(realname));
1678
	    else {
1679
1680
		size_t i = 0;

1681
1682
1683
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1684
1685
1686
		}
	    }

1687
	    backupname = charalloc(strlen(backup_dir) + strlen(backuptemp) + 1);
1688
1689
1690
	    sprintf(backupname, "%s%s", backup_dir, backuptemp);
	    free(backuptemp);
	    backuptemp = get_next_filename(backupname, "~");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1691
	    if (*backuptemp == '\0') {
1692
1693
		statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, _("Too many backup files?"));
1694
1695
		free(backuptemp);
		free(backupname);
1696
1697
1698
1699
1700
		/* 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! */
1701
		goto cleanup_and_exit;
1702
1703
1704
1705
	    } else {
		free(backupname);
		backupname = backuptemp;
	    }
1706
1707
1708
1709
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1710

1711
	/* First, unlink any existing backups.  Next, open the backup
1712
1713
	 * file with O_CREAT and O_EXCL.  If it succeeds, we have a file
	 * descriptor to a new backup file. */
1714
	if (unlink(backupname) < 0 && errno != ENOENT && !ISSET(INSECURE_BACKUP)) {
1715
1716
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1717
1718
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1719
1720
1721
1722
	    free(backupname);
	    goto cleanup_and_exit;
	}

1723
1724
	if (ISSET(INSECURE_BACKUP))
	    backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
1725
	else
1726
1727
1728
	    backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

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

1734
	if (backup_fd < 0 || backup_file == NULL) {
1735
1736
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1737
	    free(backupname);
1738
1739
1740
1741
	    /* 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! */
1742
1743
	    goto cleanup_and_exit;
	}
1744

1745
1746
1747
1748
1749
1750
	/* 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);
1751
1752
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1753
1754
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1755
1756
1757
1758
	    free(backupname);
	    goto cleanup_and_exit;
	}

1759
1760
1761
1762
	/* Set the backup's mode bits. */
	if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1 &&
			!ISSET(INSECURE_BACKUP)) {
	    fclose(backup_file);
1763
1764
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1765
1766
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1767
	    free(backupname);
1768
	    goto cleanup_and_exit;
1769
1770
1771
	}

#ifdef DEBUG
1772
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1773
1774
#endif

1775
	/* Copy the file. */
1776
1777
	if (copy_file(f, backup_file, FALSE) != 0) {
	    fclose(backup_file);
1778
	    statusline(ALERT, _("Error reading %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1779
			strerror(errno));
1780
1781
1782
	    goto cleanup_and_exit;
	}

1783
	/* And set the backup's timestamps. */
1784
1785
	if (futimens(backup_fd, filetime) == -1 && !ISSET(INSECURE_BACKUP)) {
	    fclose(backup_file);
1786
1787
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1788
1789
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1790
	    goto cleanup_and_exit;
1791
	}
1792

1793
	fclose(backup_file);
1794
1795
	free(backupname);
    }
1796

1797
    skip_backup:
1798
#endif /* !NANO_TINY */
1799

1800
1801
    if (f_open == NULL) {
	original_umask = umask(0);
1802

1803
	/* If we create a temp file, we don't let anyone else access it.
1804
1805
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1806
	    umask(S_IRWXG | S_IRWXO);
1807
1808
	else
	    umask(original_umask);
1809
    }
1810

1811
    /* If we're prepending, copy the file to a temp file. */
1812
    if (method == PREPEND) {
1813
1814
1815
	int fd_source;
	FILE *f_source = NULL;

1816
1817
1818
1819
	if (f == NULL) {
	    f = fopen(realname, "rb");

	    if (f == NULL) {
1820
		statusline(ALERT, _("Error reading %s: %s"), realname,
1821
1822
1823
1824
1825
			strerror(errno));
		goto cleanup_and_exit;
	    }
	}

1826
	tempname = safe_tempfile(&f);
1827

1828
	if (tempname == NULL) {
1829
	    statusline(ALERT, _("Error writing temp file: %s"),
1830
			strerror(errno));
1831
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1832
	}
1833

1834
	if (f_open == NULL) {
1835
	    fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1836

1837
1838
1839
	    if (fd_source != -1) {
		f_source = fdopen(fd_source, "rb");
		if (f_source == NULL) {
1840
		    statusline(ALERT, _("Error reading %s: %s"), realname,
1841
				strerror(errno));
1842
1843
1844
1845
1846
1847
		    close(fd_source);
		    fclose(f);
		    unlink(tempname);
		    goto cleanup_and_exit;
		}
	    }
1848
1849
	}

1850
	if (f_source == NULL || copy_file(f_source, f, TRUE) != 0) {
1851
	    statusline(ALERT, _("Error writing temp file: %s"),
1852
			strerror(errno));
1853
	    unlink(tempname);
1854
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1855
1856
1857
	}
    }

1858
1859
1860
    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*. */
1861
	fd = open(realname, O_WRONLY | O_CREAT | ((method == APPEND) ?
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1862
1863
		O_APPEND : (tmp ? O_EXCL : O_TRUNC)), S_IRUSR |
		S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1864

1865
1866
	/* Set the umask back to the user's original value. */
	umask(original_umask);
1867

1868
1869
	/* If we couldn't open the file, give up. */
	if (fd == -1) {
1870
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1871
			strerror(errno));
1872
1873
1874
1875
	    if (tempname != NULL)
		unlink(tempname);
	    goto cleanup_and_exit;
	}
1876

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

1879
	if (f == NULL) {
1880
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1881
			strerror(errno));
1882
1883
1884
	    close(fd);
	    goto cleanup_and_exit;
	}
1885
1886
    }

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

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

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

1895
1896
	/* Convert nulls to newlines.  data_len is the string's real
	 * length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1897
1898
	unsunder(fileptr->data, data_len);

1899
	if (size < data_len) {
1900
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1901
			strerror(errno));
1902
	    fclose(f);
1903
	    goto cleanup_and_exit;
1904
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1905

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1906
	/* If we're on the last line of the file, don't write a newline
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1907
1908
1909
1910
1911
1912
1913
	 * 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 {
1914
#ifndef NANO_TINY
1915
	    if (openfile->fmt == DOS_FILE || openfile->fmt == MAC_FILE) {
1916
		if (putc('\r', f) == EOF) {
1917
		    statusline(ALERT, _("Error writing %s: %s"), realname,
1918
				strerror(errno));
1919
1920
1921
		    fclose(f);
		    goto cleanup_and_exit;
		}
1922
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1923

1924
	    if (openfile->fmt != MAC_FILE)
1925
#endif
1926
		if (putc('\n', f) == EOF) {
1927
		    statusline(ALERT, _("Error writing %s: %s"), realname,
1928
				strerror(errno));
1929
1930
1931
1932
		    fclose(f);
		    goto cleanup_and_exit;
		}
	}
Chris Allegretta's avatar
Chris Allegretta committed
1933
1934
1935
1936
1937

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

1938
    /* If we're prepending, open the temp file, and append it to f. */
1939
    if (method == PREPEND) {
1940
1941
1942
	int fd_source;
	FILE *f_source = NULL;

1943
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1944

1945
1946
1947
1948
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1949
	}
1950

1951
	if (f_source == NULL) {
1952
1953
	    statusline(ALERT, _("Error reading %s: %s"), tempname,
			strerror(errno));
1954
	    fclose(f);
1955
	    goto cleanup_and_exit;
1956
1957
	}

1958
	if (copy_file(f_source, f, TRUE) != 0) {
1959
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1960
			strerror(errno));
1961
	    goto cleanup_and_exit;
1962
	}
1963
1964

	unlink(tempname);
1965
    } else if (fclose(f) != 0) {
1966
	statusline(ALERT, _("Error writing %s: %s"), realname,
1967
			strerror(errno));
1968
	goto cleanup_and_exit;
1969
    }
1970

1971
    if (method == OVERWRITE && !tmp) {
1972
1973
	/* If we must set the filename, and it changed, adjust things. */
	if (!nonamechange && strcmp(openfile->filename, realname) != 0) {
1974
#ifndef DISABLE_COLOR
1975
	    char *newname;
1976
	    char *oldname = openfile->syntax ? openfile->syntax->name : "";
1977
1978
1979
	    filestruct *line = openfile->fileage;
#endif
	    openfile->filename = mallocstrcpy(openfile->filename, realname);
1980

1981
1982
#ifndef DISABLE_COLOR
	    /* See if the applicable syntax has changed. */
1983
	    color_update();
1984
	    color_init();
1985

1986
	    newname = openfile->syntax ? openfile->syntax->name : "";
1987

1988
	    /* If the syntax changed, discard and recompute the multidata. */
1989
	    if (strcmp(oldname, newname) != 0) {
1990
1991
1992
1993
1994
		for (; line != NULL; line = line->next) {
		    free(line->multidata);
		    line->multidata = NULL;
		}
		precalc_multicolorinfo();
1995
		refresh_needed = TRUE;
1996
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1997
1998
#endif
	}
1999

2000
#ifndef NANO_TINY
2001
	if (!openfile->mark_set)
2002
2003
	    /* Get or update the stat info to reflect the current state. */
	    stat_with_alloc(realname, &openfile->current_stat);
2004
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2005

2006
2007
	statusline(HUSH, P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten), (unsigned long)lineswritten);
2008
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2009
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2010
    }
2011

2012
    retval = TRUE;
2013
2014
2015

  cleanup_and_exit:
    free(realname);
2016
    free(tempname);
2017

2018
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2019
2020
}

2021
#ifndef NANO_TINY
2022
2023
/* Write a marked selection from a file out to disk.  Return TRUE on
 * success or FALSE on error. */
2024
bool write_marked_file(const char *name, FILE *f_open, bool tmp,
2025
	kind_of_writing_type method)
2026
{
2027
    bool retval;
2028
    bool old_modified = openfile->modified;
2029
	/* Save the status, because write_file() unsets the modified flag. */
2030
    bool added_magicline = FALSE;
2031
2032
2033
2034
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

2035
    /* Partition the buffer so that it contains only the marked text. */
2036
    mark_order((const filestruct **)&top, &top_x,
2037
		(const filestruct **)&bot, &bot_x, NULL);
2038
    filepart = partition_filestruct(top, top_x, bot, bot_x);
2039

2040
2041
2042
    /* 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') {
2043
	new_magicline();
2044
2045
	added_magicline = TRUE;
    }
2046

2047
    retval = write_file(name, f_open, tmp, method, TRUE);
2048

2049
2050
    /* If we added a magicline, remove it now. */
    if (added_magicline)
2051
2052
	remove_magicline();

2053
    /* Unpartition the buffer so that it contains all the text again. */
2054
    unpartition_filestruct(&filepart);
2055

2056
    if (old_modified)
2057
2058
2059
2060
	set_modified();

    return retval;
}
2061

2062
#endif /* !NANO_TINY */
2063

2064
/* Write the current file to disk.  If the mark is on, write the current
2065
2066
2067
2068
2069
 * 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
2070
{
2071
    int i;
2072
    bool result = FALSE;
2073
    kind_of_writing_type method = OVERWRITE;
2074
2075
    char *given;
	/* The filename we offer, or what the user typed so far. */
2076
    bool maychange = (openfile->filename[0] == '\0');
2077
	/* Whether it's okay to save the file under a different name. */
2078
#ifndef DISABLE_EXTRA
2079
    static bool did_credits = FALSE;
2080
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2081

2082
    /* Display newlines in filenames as ^J. */
2083
2084
    as_an_at = FALSE;

2085
2086
2087
2088
    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
2089
2090
    }

2091
    given = mallocstrcpy(NULL,
2092
#ifndef NANO_TINY
2093
	(openfile->mark_set && !exiting) ? "" :
2094
#endif
2095
	openfile->filename);
2096
2097
2098

    while (TRUE) {
	const char *msg;
2099
#ifndef NANO_TINY
2100
2101
	const char *formatstr, *backupstr;

2102
	formatstr = (openfile->fmt == DOS_FILE) ? _(" [DOS Format]") :
2103
			(openfile->fmt == MAC_FILE) ? _(" [Mac Format]") : "";
2104

2105
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2106

2107
	/* If we're using restricted mode, don't display the "Write
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2108
2109
2110
	 * Selection to File" prompt.  This function is disabled, since
	 * it allows reading from or writing to files not specified on
	 * the command line. */
2111
	if (openfile->mark_set && !exiting && !ISSET(RESTRICTED))
2112
	    /* TRANSLATORS: The next six strings are prompts. */
2113
2114
	    msg = (method == PREPEND) ? _("Prepend Selection to File") :
			(method == APPEND) ? _("Append Selection to File") :
2115
			_("Write Selection to File");
2116
2117
2118
	else if (method != OVERWRITE)
	    msg = (method == PREPEND) ? _("File Name to Prepend to") :
					_("File Name to Append to");
2119
	else
2120
#endif /* !NANO_TINY */
2121
	    msg = _("File Name to Write");
2122

2123
2124
	present_path = mallocstrcpy(present_path, "./");

2125
2126
	/* If we're using restricted mode, and the filename isn't blank,
	 * disable tab completion. */
2127
	i = do_prompt(!ISSET(RESTRICTED) || openfile->filename[0] == '\0',
2128
		TRUE, MWRITEFILE, given,
2129
#ifndef DISABLE_HISTORIES
2130
2131
		NULL,
#endif
2132
		edit_refresh, "%s%s%s", msg,
2133
2134
#ifndef NANO_TINY
		formatstr, backupstr
2135
#else
2136
		"", ""
2137
2138
2139
#endif
		);

2140
	if (i < 0) {
2141
	    statusbar(_("Cancelled"));
2142
2143
	    break;
	} else {
2144
2145
	    functionptrtype func = func_from_key(&i);

2146
	    /* Upon request, abandon the buffer. */
2147
	    if (func == discard_buffer) {
2148
2149
		free(given);
		return 2;
2150
2151
	    }

2152
	    given = mallocstrcpy(given, answer);
Chris Allegretta's avatar
Chris Allegretta committed
2153

2154
#ifdef ENABLE_BROWSER
2155
	    if (func == to_files_void) {
2156
		char *chosen = do_browse_from(answer);
2157

2158
		if (chosen == NULL)
2159
		    continue;
2160

2161
		/* We have a file now.  Indicate this. */
2162
		free(answer);
2163
		answer = chosen;
2164
	    } else
2165
#endif
2166
#ifndef NANO_TINY
2167
	    if (func == dos_format_void) {
2168
2169
		openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE :
			DOS_FILE;
2170
		continue;
2171
	    } else if (func == mac_format_void) {
2172
2173
		openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE :
			MAC_FILE;
2174
		continue;
2175
	    } else if (func == backup_file_void) {
2176
2177
		TOGGLE(BACKUP_FILE);
		continue;
2178
	    } else if (func == prepend_void) {
2179
		method = (method == PREPEND) ? OVERWRITE : PREPEND;
2180
		continue;
2181
	    } else if (func == append_void) {
2182
		method = (method == APPEND) ? OVERWRITE : APPEND;
2183
		continue;
2184
2185
2186
	    } else
#endif /* !NANO_TINY */
	    if (func == do_help_void) {
2187
		continue;
2188
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2189

Chris Allegretta's avatar
Chris Allegretta committed
2190
#ifdef DEBUG
2191
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
2192
#endif
2193

2194
#ifndef DISABLE_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2195
2196
2197
2198
2199
2200
2201
	    /* 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) &&
2202
			strcasecmp(answer, "zzy") == 0) {
2203
		do_credits();
2204
		did_credits = TRUE;
2205
2206
		break;
	    }
2207
#endif
2208

2209
	    if (method == OVERWRITE) {
2210
		bool name_exists, do_warning;
2211
		char *full_answer, *full_filename;
2212
2213
		struct stat st;

2214
2215
		full_answer = get_full_path(answer);
		full_filename = get_full_path(openfile->filename);
2216
2217
		name_exists = (stat((full_answer == NULL) ?
				answer : full_answer, &st) != -1);
2218
2219
2220
2221
		if (openfile->filename[0] == '\0')
		    do_warning = name_exists;
		else
		    do_warning = (strcmp((full_answer == NULL) ?
2222
2223
				answer : full_answer, (full_filename == NULL) ?
				openfile->filename : full_filename) != 0);
2224

2225
2226
		free(full_filename);
		free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2227

2228
		if (do_warning) {
2229
2230
2231
		    /* 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. */
2232
		    if (ISSET(RESTRICTED)) {
2233
			/* TRANSLATORS: Restricted mode forbids overwriting. */
2234
2235
			warn_and_shortly_pause(_("File exists -- "
					"cannot overwrite"));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2236
			continue;
2237
		    }
2238

2239
		    if (!maychange) {
2240
#ifndef NANO_TINY
2241
2242
2243
			if (exiting || !openfile->mark_set)
#endif
			{
2244
2245
			    if (do_yesno_prompt(FALSE, _("Save file under "
					"DIFFERENT NAME? ")) < 1)
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
				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)
2261
2262
			    continue;
		    }
2263
		}
2264
#ifndef NANO_TINY
2265
2266
2267
		/* Complain if the file exists, the name hasn't changed,
		 * and the stat information we had before does not match
		 * what we have now. */
2268
2269
2270
2271
		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)) {
2272

2273
2274
		    warn_and_shortly_pause(_("File on disk has changed"));

2275
2276
		    if (do_yesno_prompt(FALSE, _("File was modified since "
				"you opened it; continue saving? ")) < 1)
2277
2278
			continue;
		}
2279
#endif
2280
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2281

2282
	    /* Here's where we allow the selected text to be written to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2283
2284
2285
	     * 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. */
2286
#ifndef NANO_TINY
2287
	    if (openfile->mark_set && !exiting && !ISSET(RESTRICTED))
2288
		result = write_marked_file(answer, NULL, FALSE, method);
2289
	    else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2290
#endif
2291
		result = write_file(answer, NULL, FALSE, method, FALSE);
2292

2293
2294
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2295
    }
2296

2297
    free(given);
2298

2299
    return result ? 1 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
2300
2301
}

2302
/* Write the current buffer to disk, or discard it. */
2303
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
2304
{
2305
2306
2307
    /* 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
2308
}
Chris Allegretta's avatar
Chris Allegretta committed
2309

2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
#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

2321
2322
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
2323
char *real_dir_from_tilde(const char *buf)
2324
{
2325
    char *retval;
2326

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2327
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2328
	size_t i = 1;
2329
	char *tilde_dir;
2330

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

2335
2336
2337
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
2338
	    tilde_dir = mallocstrcpy(NULL, homedir);
2339
	} else {
2340
#ifdef HAVE_PWD_H
2341
2342
	    const struct passwd *userdata;

2343
2344
2345
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

2346
2347
	    do {
		userdata = getpwent();
2348
2349
	    } while (userdata != NULL &&
			strcmp(userdata->pw_name, tilde_dir + 1) != 0);
2350
	    endpwent();
2351
	    if (userdata != NULL)
2352
		tilde_dir = mallocstrcpy(tilde_dir, userdata->pw_dir);
2353
2354
2355
#else
	    tilde_dir = strdup("");
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2356
	}
2357

2358
2359
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2360

2361
2362
2363
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2364

2365
    return retval;
2366
2367
}

2368
#if defined(ENABLE_TABCOMP) || defined(ENABLE_BROWSER)
2369
/* Our sort routine for file listings.  Sort alphabetically and
2370
 * case-insensitively, and sort directories before filenames. */
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
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;

2384
2385
2386
    /* 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
2387
     * have to use multibyte strcasecmp() instead. */
2388
    return mbstrcasecmp(a, b);
2389
}
2390
2391
2392
2393
2394

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

2398
2399
    for (; len > 0; len--)
	free(array[len - 1]);
2400

2401
2402
    free(array);
}
2403
2404
#endif

2405
#ifdef ENABLE_TABCOMP
2406
/* Is the given path a directory? */
2407
bool is_dir(const char *buf)
2408
{
2409
    char *dirptr;
2410
    struct stat fileinfo;
2411
2412
2413
2414
    bool retval;

    dirptr = real_dir_from_tilde(buf);

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

2417
    free(dirptr);
2418

2419
    return retval;
2420
}
Chris Allegretta's avatar
Chris Allegretta committed
2421

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2422
2423
2424
/* 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
2425
 * file, with the copyright years updated:
Chris Allegretta's avatar
Chris Allegretta committed
2426
2427
2428
 *
 * Termios command line History and Editting, originally
 * intended for NetBSD sh (ash)
2429
 * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
Chris Allegretta's avatar
Chris Allegretta committed
2430
2431
2432
2433
2434
2435
2436
2437
 *      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.
2438
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
2439

2440
/* We consider the first buf_len characters of buf for ~username tab
2441
2442
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
2443
	size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2444
{
2445
    char **matches = NULL;
2446
2447
2448
#ifdef HAVE_PWD_H
    const struct passwd *userdata;
#endif
2449

2450
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2451

2452
    *num_matches = 0;
2453

2454
#ifdef HAVE_PWD_H
2455
    while ((userdata = getpwent()) != NULL) {
2456
	if (strncmp(userdata->pw_name, buf + 1, buf_len - 1) == 0) {
2457
2458
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2459

2460
2461
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2462
2463
2464
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2465
2466
#endif

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2467
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
2468
2469
					sizeof(char *));
	    matches[*num_matches] = charalloc(strlen(userdata->pw_name) + 2);
2470
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2471
	    ++(*num_matches);
2472
	}
2473
2474
    }
    endpwent();
2475
#endif
2476

2477
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2478
2479
}

2480
/* We consider the first buf_len characters of buf for filename tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2481
 * completion. */
2482
char **cwd_tab_completion(const char *buf, bool allow_files, size_t
2483
	*num_matches, size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2484
{
2485
2486
    char *dirname = mallocstrcpy(NULL, buf);
    char *slash, *filename;
2487
2488
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2489
    DIR *dir;
2490
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2491

2492
    *num_matches = 0;
2493
    dirname[buf_len] = '\0';
2494

2495
    /* If there's a / in the name, split out filename and directory parts. */
2496
2497
2498
    slash = strrchr(dirname, '/');
    if (slash != NULL) {
	char *wasdirname = dirname;
2499

2500
2501
	filename = mallocstrcpy(NULL, ++slash);
	/* Cut off the filename part after the slash. */
2502
	*slash = '\0';
2503
	dirname = real_dir_from_tilde(dirname);
2504
2505
2506
2507
2508
2509
	/* 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);
	}
2510
	free(wasdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2511
    } else {
2512
	filename = dirname;
2513
	dirname = mallocstrcpy(NULL, present_path);
Chris Allegretta's avatar
Chris Allegretta committed
2514
2515
    }

2516
    assert(dirname[strlen(dirname) - 1] == '/');
2517

Chris Allegretta's avatar
Chris Allegretta committed
2518
    dir = opendir(dirname);
2519

2520
    if (dir == NULL) {
2521
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2522
	beep();
2523
2524
2525
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2526
    }
2527
2528
2529

    filenamelen = strlen(filename);

2530
    while ((nextdir = readdir(dir)) != NULL) {
2531
2532
	bool skip_match = FALSE;

Chris Allegretta's avatar
Chris Allegretta committed
2533
#ifdef DEBUG
2534
	fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2535
#endif
2536
	/* See if this matches. */
2537
	if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
2538
2539
		(*filename == '.' || (strcmp(nextdir->d_name, ".") != 0 &&
		strcmp(nextdir->d_name, "..") != 0))) {
2540
2541
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2542

2543
	    char *tmp = charalloc(strlen(dirname) + strlen(nextdir->d_name) + 1);
2544
2545
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

2546
2547
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2548
2549
2550
2551
2552
2553
2554
	     * 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. */
2555
	    if (!allow_files && !is_dir(tmp))
2556
2557
2558
		skip_match = TRUE;

	    free(tmp);
2559

2560
	    if (skip_match)
2561
		continue;
2562

2563
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
2564
					sizeof(char *));
2565
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2566
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2567
2568
	}
    }
2569

2570
2571
    closedir(dir);
    free(dirname);
2572
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2573

2574
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2575
2576
}

2577
/* Do tab completion.  place refers to how much the statusbar cursor
2578
2579
 * position should be advanced.  refresh_func is the function we will
 * call to refresh the edit window. */
2580
char *input_tab(char *buf, bool allow_files, size_t *place,
2581
	bool *lastwastab, void (*refresh_func)(void), bool *listed)
Chris Allegretta's avatar
Chris Allegretta committed
2582
{
2583
    size_t num_matches = 0, buf_len;
2584
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2585

2586
    assert(buf != NULL && place != NULL && *place <= strlen(buf) &&
2587
2588
2589
		lastwastab != NULL && refresh_func != NULL && listed != NULL);

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

2591
2592
    /* 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
2593
    if (*place > 0 && *buf == '~') {
2594
	const char *slash = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2595

2596
	if (slash == NULL || slash >= buf + *place)
2597
	    matches = username_tab_completion(buf, &num_matches, *place);
2598
    }
2599

2600
2601
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
2602
	matches = cwd_tab_completion(buf, allow_files, &num_matches, *place);
2603

2604
2605
2606
    buf_len = strlen(buf);

    if (num_matches == 0 || *place != buf_len)
2607
2608
2609
	beep();
    else {
	size_t match, common_len = 0;
2610
	char *mzero, *glued;
2611
	const char *lastslash = revstrstr(buf, "/", buf + *place);
2612
	size_t lastslash_len = (lastslash == NULL) ? 0 : lastslash - buf + 1;
2613
	char char1[MAXCHARLEN], char2[MAXCHARLEN];
2614
	int len1, len2;
2615

2616
	/* Get the number of characters that all matches have in common. */
2617
	while (TRUE) {
2618
	    len1 = parse_mbchar(matches[0] + common_len, char1, NULL);
2619
2620

	    for (match = 1; match < num_matches; match++) {
2621
2622
2623
		len2 = parse_mbchar(matches[match] + common_len, char2, NULL);

		if (len1 != len2 || strncmp(char1, char2, len2) != 0)
2624
2625
		    break;
	    }
2626

2627
	    if (match < num_matches || matches[0][common_len] == '\0')
2628
		break;
2629

2630
	    common_len += len1;
2631
	}
2632

2633
	mzero = charalloc(lastslash_len + common_len + 1);
2634
2635
2636

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

2638
	common_len += lastslash_len;
2639
	mzero[common_len] = '\0';
2640

2641
2642
2643
2644
	/* 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);

2645
	assert(common_len >= *place);
2646

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

2650
2651
	    assert(common_len > *place);
	}
2652

2653
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2654
	    beep();
2655

2656
	/* If the matches have something in common, show that part. */
2657
	if (common_len != *place) {
2658
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
2659
	    charmove(buf + common_len, buf + *place, buf_len - *place + 1);
2660
	    strncpy(buf, mzero, common_len);
2661
	    *place = common_len;
2662
2663
2664
	}

	if (!*lastwastab)
2665
	    *lastwastab = TRUE;
2666
	else if (num_matches > 1) {
2667
	    int longest_name = 0, ncols, editline = 0;
2668

2669
	    /* Sort the list of available choices. */
2670
2671
	    qsort(matches, num_matches, sizeof(char *), diralphasort);

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

2676
2677
		if (namelen > longest_name)
		    longest_name = namelen;
2678
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2679

2680
2681
	    if (longest_name > COLS - 1)
		longest_name = COLS - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2682

2683
	    /* Each column will be (longest_name + 2) columns wide, i.e.
2684
2685
	     * two spaces between columns, except that there will be
	     * only one space after the last column. */
2686
	    ncols = (COLS + 1) / (longest_name + 2);
2687

2688
	    /* Blank the edit window and hide the cursor. */
2689
2690
	    blank_edit();
	    curs_set(0);
2691
	    wmove(edit, 0, 0);
2692

2693
	    /* Now print the list of matches out there. */
2694
2695
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2696

2697
		wmove(edit, editline, (longest_name + 2) * (match % ncols));
2698

2699
		if (match % ncols == 0 && editline == editwinrows - 1 &&
2700
			num_matches - match > ncols) {
2701
2702
2703
		    waddstr(edit, _("(more)"));
		    break;
		}
2704

2705
		disp = display_string(matches[match], 0, longest_name, FALSE);
2706
2707
		waddstr(edit, disp);
		free(disp);
2708

2709
		if ((match + 1) % ncols == 0)
2710
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2711
	    }
2712

2713
	    wnoutrefresh(edit);
2714
	    *listed = TRUE;
2715
2716
	}

2717
	free(glued);
2718
	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2719
2720
    }

2721
    free_chararray(matches, num_matches);
2722

2723
2724
2725
    /* 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)
2726
2727
	refresh_func();

2728
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2729
}
2730
#endif /* ENABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2731

2732
2733
/* Return the filename part of the given path. */
const char *tail(const char *path)
2734
{
2735
    const char *slash = strrchr(path, '/');
2736

2737
2738
    if (slash == NULL)
	return path;
2739
    else
2740
	return ++slash;
2741
2742
}

2743
2744
#ifndef DISABLE_HISTORIES
/* Return the constructed dirfile path, or NULL if we can't find the home
2745
 * directory.  The string is dynamically allocated, and should be freed. */
2746
char *construct_filename(const char *str)
2747
{
2748
    char *newstr = NULL;
2749

2750
2751
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2752

2753
2754
2755
	newstr = charalloc(homelen + strlen(str) + 1);
	strcpy(newstr, homedir);
	strcpy(newstr + homelen, str);
Chris Allegretta's avatar
Chris Allegretta committed
2756
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2757

2758
2759
2760
2761
2762
    return newstr;
}

char *histfilename(void)
{
2763
    return construct_filename("/.nano/search_history");
2764
2765
}

2766
2767
/* Construct the legacy history filename. */
/* (To be removed in 2018.) */
2768
2769
2770
2771
2772
2773
2774
char *legacyhistfilename(void)
{
    return construct_filename("/.nano_history");
}

char *poshistfilename(void)
{
2775
    return construct_filename("/.nano/filepos_history");
2776
2777
2778
2779
}

void history_error(const char *msg, ...)
{
2780
    va_list ap;
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790

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

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

2791
2792
2793
/* 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. */
2794
2795
int check_dotnano(void)
{
2796
    int ret = 1;
2797
2798
2799
2800
2801
    struct stat dirstat;
    char *nanodir = construct_filename("/.nano");

    if (stat(nanodir, &dirstat) == -1) {
	if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
2802
	    history_error(N_("Unable to create directory %s: %s\n"
2803
2804
				"It is required for saving/loading "
				"search history or cursor positions.\n"),
2805
				nanodir, strerror(errno));
2806
	    ret = 0;
2807
2808
	}
    } else if (!S_ISDIR(dirstat.st_mode)) {
2809
	history_error(N_("Path %s is not a directory and needs to be.\n"
2810
2811
				"Nano will be unable to load or save "
				"search history or cursor positions.\n"),
2812
				nanodir);
2813
	ret = 0;
2814
    }
2815
2816
2817

    free(nanodir);
    return ret;
2818
2819
}

2820
/* Load the search and replace histories from ~/.nano/search_history. */
2821
2822
2823
void load_history(void)
{
    char *nanohist = histfilename();
2824
2825
2826
    char *legacyhist = legacyhistfilename();
    struct stat hstat;

2827
2828
    /* If there is an old history file, migrate it. */
    /* (To be removed in 2018.) */
2829
    if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
2830
	if (rename(legacyhist, nanohist) == -1)
2831
2832
2833
	    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));
2834
	else
2835
2836
2837
	    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);
2838
2839
    }

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

2844
	if (hist == NULL) {
2845
	    if (errno != ENOENT) {
2846
2847
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2848
		history_error(N_("Error reading %s: %s"), nanohist,
2849
			strerror(errno));
2850
	    }
2851
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2852
2853
2854
	    /* 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. */
2855
	    filestruct **history = &search_history;
2856
	    char *line = NULL;
2857
	    size_t buf_len = 0;
2858
2859
	    ssize_t read;

2860
2861
	    while ((read = getline(&line, &buf_len, hist)) > 0) {
		line[--read] = '\0';
2862
2863
2864
		if (read > 0) {
		    /* Encode any embedded NUL as 0x0A. */
		    unsunder(line, read);
2865
		    update_history(history, line);
2866
		} else
2867
2868
		    history = &replace_history;
	    }
2869

2870
	    fclose(hist);
2871
	    free(line);
2872
	}
2873
	free(nanohist);
2874
	free(legacyhist);
2875
2876
2877
    }
}

2878
/* Write the lines of a history list, starting with the line at head, to
2879
2880
 * the open file at hist.  Return TRUE if the write succeeded, and FALSE
 * otherwise. */
2881
bool writehist(FILE *hist, const filestruct *head)
2882
{
2883
    const filestruct *item;
2884

2885
    /* Write a history list, from the oldest item to the newest. */
2886
2887
    for (item = head; item != NULL; item = item->next) {
	size_t length = strlen(item->data);
2888

2889
2890
2891
	/* Decode 0x0A bytes as embedded NULs. */
	sunder(item->data);

2892
2893
2894
	if (fwrite(item->data, sizeof(char), length, hist) < length)
	    return FALSE;
	if (putc('\n', hist) == EOF)
2895
	    return FALSE;
2896
    }
2897

2898
    return TRUE;
2899
2900
}

2901
/* Save the search and replace histories to ~/.nano/search_history. */
2902
2903
void save_history(void)
{
2904
    char *nanohist;
2905

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2906
    /* Don't save unchanged or empty histories. */
2907
    if (!history_has_changed() || (searchbot->lineno == 1 &&
2908
		replacebot->lineno == 1))
2909
2910
	return;

2911
2912
2913
2914
    nanohist = histfilename();

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

2916
	if (hist == NULL)
2917
2918
	    fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
			strerror(errno));
2919
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2920
2921
	    /* Make sure no one else can read from or write to the
	     * history file. */
2922
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2923

2924
	    if (!writehist(hist, searchage) || !writehist(hist, replaceage))
2925
		fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
2926
			strerror(errno));
2927

2928
2929
	    fclose(hist);
	}
2930

2931
2932
2933
	free(nanohist);
    }
}
2934

2935
/* Save the recorded last file positions to ~/.nano/filepos_history. */
2936
2937
void save_poshistory(void)
{
2938
    char *poshist = poshistfilename();
2939
    poshiststruct *posptr;
2940
    FILE *hist;
2941

2942
2943
    if (poshist == NULL)
	return;
2944

2945
    hist = fopen(poshist, "wb");
2946

2947
2948
2949
2950
2951
    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);
2952

2953
	for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2954
	    char *path_and_place;
2955
2956
	    size_t length;

2957
2958
	    /* Assume 20 decimal positions each for line and column number,
	     * plus two spaces, plus the line feed, plus the null byte. */
2959
2960
	    path_and_place = charalloc(strlen(posptr->filename) + 44);
	    sprintf(path_and_place, "%s %ld %ld\n", posptr->filename,
2961
			(long)posptr->lineno, (long)posptr->xno);
2962
	    length = strlen(path_and_place);
2963
2964

	    /* Encode newlines in filenames as nulls. */
2965
	    sunder(path_and_place);
2966
	    /* Restore the terminating newline. */
2967
	    path_and_place[length - 1] = '\n';
2968

2969
	    if (fwrite(path_and_place, sizeof(char), length, hist) < length)
2970
2971
		fprintf(stderr, _("Error writing %s: %s\n"),
					poshist, strerror(errno));
2972
	    free(path_and_place);
2973
	}
2974
	fclose(hist);
2975
    }
2976
    free(poshist);
2977
2978
}

2979
2980
/* 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. */
2981
2982
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
2983
    poshiststruct *posptr, *theone, *posprev = NULL;
2984
    char *fullpath = get_full_path(filename);
2985

2986
    if (fullpath == NULL || fullpath[strlen(fullpath) - 1] == '/' || inhelp) {
2987
	free(fullpath);
2988
	return;
2989
    }
2990

2991
    /* Look for a matching filename in the list. */
2992
    for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2993
2994
	if (!strcmp(posptr->filename, fullpath))
	    break;
2995
2996
2997
	posprev = posptr;
    }

2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
    /* 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;
    }

3012
    theone = posptr;
3013

3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
    /* 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) {
3024
3025
3026
3027
	if (posprev == NULL)
	    position_history = posptr->next;
	else
	    posprev->next = posptr->next;
3028
3029
3030
3031
3032
3033
3034
3035
3036
	while (posptr->next != NULL)
	    posptr = posptr->next;
	posptr->next = theone;
    }

    /* Store the last cursor position. */
    theone->lineno = lineno;
    theone->xno = xpos;
    theone->next = NULL;
3037
3038
3039
3040

    free(fullpath);
}

3041
3042
3043
3044
/* 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)
3045
{
3046
    poshiststruct *posptr = position_history;
3047
3048
3049
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
3050
3051
3052
3053
	return FALSE;

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

3055
    free(fullpath);
3056
3057

    if (posptr == NULL)
3058
	return FALSE;
3059
3060
3061
3062

    *line = posptr->lineno;
    *column = posptr->xno;
    return TRUE;
3063
3064
}

3065
/* Load the recorded file positions from ~/.nano/filepos_history. */
3066
3067
void load_poshistory(void)
{
3068
    char *poshist = poshistfilename();
3069
    FILE *hist;
3070

3071
3072
3073
    /* If the home directory is missing, do_rcfile() will have reported it. */
    if (poshist == NULL)
	return;
3074

3075
    hist = fopen(poshist, "rb");
3076

3077
3078
    if (hist == NULL) {
	if (errno != ENOENT) {
3079
	    /* When reading failed, don't save history when we quit. */
3080
3081
	    UNSET(POS_HISTORY);
	    history_error(N_("Error reading %s: %s"), poshist, strerror(errno));
3082
	}
3083
3084
3085
    } else {
	char *line = NULL, *lineptr, *xptr;
	size_t buf_len = 0;
3086
	ssize_t read, count = 0;
3087
3088
3089
	poshiststruct *record_ptr = NULL, *newrecord;

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

3094
3095
	    /* Find where the x index and line number are in the line. */
	    xptr = revstrstr(line, " ", line + read - 3);
3096
3097
	    if (xptr == NULL)
		continue;
3098
	    lineptr = revstrstr(line, " ", xptr - 2);
3099
3100
	    if (lineptr == NULL)
		continue;
3101
3102
3103
3104

	    /* Now separate the three elements of the line. */
	    *(xptr++) = '\0';
	    *(lineptr++) = '\0';
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119

	    /* 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;
3120
3121

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

3125
		position_history = position_history->next;
3126
3127
3128
3129

		free(drop_record->filename);
		free(drop_record);
	    }
3130
3131
3132
	}
	fclose(hist);
	free(line);
3133
    }
3134
    free(poshist);
3135
}
3136
#endif /* !DISABLE_HISTORIES */