files.c 90.4 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/**************************************************************************
2
 *   files.c  --  This file is part of GNU nano.                          *
Chris Allegretta's avatar
Chris Allegretta committed
3
 *                                                                        *
4
 *   Copyright (C) 1999-2011, 2013-2017 Free Software Foundation, Inc.    *
5
6
 *   Copyright (C) 2015, 2016 Benno Schulenberg                           *
 *                                                                        *
7
8
9
10
 *   GNU nano is free software: you can redistribute it and/or modify     *
 *   it under the terms of the GNU General Public License as published    *
 *   by the Free Software Foundation, either version 3 of the License,    *
 *   or (at your option) any later version.                               *
Chris Allegretta's avatar
Chris Allegretta committed
11
 *                                                                        *
12
13
14
15
 *   GNU nano is distributed in the hope that it will be useful,          *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty          *
 *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.              *
 *   See the GNU General Public License for more details.                 *
Chris Allegretta's avatar
Chris Allegretta committed
16
17
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
18
 *   along with this program.  If not, see http://www.gnu.org/licenses/.  *
Chris Allegretta's avatar
Chris Allegretta committed
19
20
21
 *                                                                        *
 **************************************************************************/

22
#include "proto.h"
23

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
    assert(filename != NULL);

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

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

446
447
    realname = real_dir_from_tilde(filename);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    if (descriptor < 0)
	return;

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

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

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

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

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

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

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

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

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

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

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

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

634
635
636
637
638
639
    if (inhelp)
	return;

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

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

#ifdef DEBUG
646
    dump_filestruct(openfile->current);
647
#endif
Chris Allegretta's avatar
Chris Allegretta committed
648
649
}

650
/* Switch to the previous entry in the openfile filebuffer. */
651
void switch_to_prev_buffer_void(void)
652
{
653
    switch_to_prevnext_buffer(FALSE);
654
}
655

656
/* Switch to the next entry in the openfile filebuffer. */
657
void switch_to_next_buffer_void(void)
658
{
659
    switch_to_prevnext_buffer(TRUE);
660
}
661

662
663
664
665
/* 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)
666
{
667
    assert(openfile != NULL);
668

669
    /* If only one file buffer is open, get out. */
670
    if (openfile == openfile->next)
671
	return FALSE;
672

673
#ifndef DISABLE_HISTORIES
674
675
676
    if (ISSET(POS_HISTORY))
	update_poshistory(openfile->filename,
			openfile->current->lineno, xplustabs() + 1);
677
#endif
678

679
    /* Switch to the next file buffer. */
680
    switch_to_prevnext_buffer(TRUE);
681

682
    /* Close the file buffer we had open before. */
683
    unlink_opennode(openfile->prev);
684

685
    /* If now just one buffer remains open, show "Exit" in the help lines. */
686
687
    if (openfile == openfile->next)
	exitfunc->desc = exit_tag;
688

689
    return TRUE;
690
}
691
#endif /* ENABLE_MULTIBUFFER */
692

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

    if (ISSET(VIEW_MODE))
	return TRUE;

708
    assert(filename != NULL);
709
710
711
712

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

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

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

726
    close(fd);
727
    free(full_filename);
728
729

    return result;
730
731
}

732
733
734
/* 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)
735
{
736
    unsunder(buf, buf_len);
737
    buf[buf_len] = '\0';
738

739
    return mallocstrcpy(NULL, buf);
740
}
741

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

778
    buf = charalloc(bufx);
779

780
781
782
#ifndef NANO_TINY
    if (undoable)
	add_undo(INSERT);
783
784

    if (ISSET(SOFTWRAP))
785
	was_leftedge = leftedge_for(xplustabs(), openfile->current);
786
787
#endif

788
789
790
791
792
    /* Create an empty buffer. */
    topline = make_new_node(NULL);
    bottomline = topline;

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

824
825
	    /* Keep track of the total length of the line.  It might have
	     * nulls in it, so we can't just use strlen() later. */
826
	    len++;
827

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

#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
858
859
860
861
862
863
    }

    /* Perhaps this could use some better handling. */
    if (ferror(f))
	nperror(filename);
    fclose(f);
864
    if (fd > 0 && checkwritable) {
865
	close(fd);
866
867
	writable = is_file_writable(filename);
    }
868

869
870
871
872
873
874
875
    /* 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;

876
#ifndef NANO_TINY
877
878
879
880
881
882
	/* 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;
883

884
885
	    /* Strip the carriage return. */
	    buf[--len] = '\0';
886

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

895
896
897
898
	if (mac_line_needs_newline) {
	    bottomline->next = make_new_node(bottomline);
	    bottomline = bottomline->next;
	    bottomline->data = mallocstrcpy(NULL, "");
899
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
900
    }
901

902
    free(buf);
903

904
905
    /* Insert the just read buffer into the current one. */
    ingraft_buffer(topline);
906

907
    /* Set the desired x position at the end of what was inserted. */
908
909
    openfile->placewewant = xplustabs();

910
911
912
913
    /* If we've read a help file, don't give any feedback. */
    if (inhelp)
	return;

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

938
939
    /* If we inserted less than a screenful, don't center the cursor. */
    if (less_than_a_screenful(was_lineno, was_leftedge))
940
941
	focusing = FALSE;

942
#ifndef NANO_TINY
943
944
945
    if (undoable)
	update_undo(INSERT);

946
947
    if (ISSET(MAKE_IT_UNIX))
	openfile->fmt = NIX_FILE;
948
#endif
949
}
Chris Allegretta's avatar
Chris Allegretta committed
950

951
952
/* 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.
953
 * Return -2 if we say "New File", -1 if the file isn't opened, and the
954
 * obtained fd otherwise.  *f is set to the opened file. */
955
int open_file(const char *filename, bool newfie, bool quiet, FILE **f)
956
{
957
    struct stat fileinfo, fileinfo2;
958
    int fd;
959
    char *full_filename;
960

961
    assert(filename != NULL && f != NULL);
962

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

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

    if (stat(full_filename, &fileinfo) == -1) {
973
	if (newfie) {
974
	    if (!quiet)
975
		statusbar(_("New File"));
976
	    free(full_filename);
977
978
	    return -2;
	}
979

980
	statusline(ALERT, _("File \"%s\" not found"), filename);
981
	free(full_filename);
982
983
	return -1;
    }
984

985
986
987
    /* 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)) {
988
	statusline(ALERT, S_ISDIR(fileinfo.st_mode) ?
989
990
			_("\"%s\" is a directory") :
			_("\"%s\" is a device file"), filename);
991
	free(full_filename);
992
	return -1;
993
994
995
996
997
998
999
1000
1001
    }

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

1004
	if (*f == NULL) {
1005
	    statusline(ALERT, _("Error reading %s: %s"), filename, strerror(errno));
1006
	    close(fd);
1007
	} else if (!inhelp)
1008
	    statusbar(_("Reading File"));
1009
    }
1010

1011
1012
    free(full_filename);

1013
1014
1015
    return fd;
}

1016
1017
1018
1019
1020
/* 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)
1021
{
1022
1023
    unsigned long i = 0;
    char *buf;
1024
    size_t wholenamelen;
1025

1026
    assert(name != NULL && suffix != NULL);
1027

1028
    wholenamelen = strlen(name) + strlen(suffix);
1029

1030
1031
1032
    /* Reserve space for: the name plus the suffix plus a dot plus
     * possibly five digits plus a null byte. */
    buf = charalloc(wholenamelen + 7);
1033
    sprintf(buf, "%s%s", name, suffix);
1034

1035
1036
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
1037

1038
1039
	if (stat(buf, &fs) == -1)
	    return buf;
1040
1041
1042

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

1045
	sprintf(buf + wholenamelen, ".%lu", i);
1046
    }
Chris Allegretta's avatar
Chris Allegretta committed
1047

1048
1049
    /* There is no possible save file: blank out the filename. */
    *buf = '\0';
1050

1051
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
1052
1053
}

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

1066
    /* Display newlines in filenames as ^J. */
1067
1068
    as_an_at = FALSE;

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

1090
1091
	present_path = mallocstrcpy(present_path, "./");

1092
	i = do_prompt(TRUE, TRUE,
1093
#ifndef NANO_TINY
1094
		execute ? MEXTCMD :
1095
#endif
1096
		MINSERTFILE, given,
1097
#ifndef DISABLE_HISTORIES
1098
		NULL,
1099
#endif
1100
		edit_refresh, msg,
1101
#ifndef DISABLE_OPERATINGDIR
1102
		operating_dir != NULL ? operating_dir :
1103
#endif
1104
		"./");
1105

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

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

1139
		/* If no file was chosen, go back to the prompt. */
1140
		if (chosen == NULL)
1141
		    continue;
1142

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

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

1162
#ifdef ENABLE_MULTIBUFFER
1163
		/* If this is a new buffer, put the cursor at the top. */
1164
1165
1166
1167
		if (ISSET(MULTIBUFFER)) {
		    openfile->current = openfile->fileage;
		    openfile->current_x = 0;
		    openfile->placewewant = 0;
1168
1169

		    set_modified();
1170
		}
1171
#endif
1172
	    } else
1173
#endif /* !NANO_TINY */
1174
	    {
1175
1176
		/* Make sure the path to the file specified in answer is
		 * tilde-expanded. */
1177
		answer = free_and_assign(answer, real_dir_from_tilde(answer));
1178

1179
		/* Save the file specified in answer in the current buffer. */
1180
		open_buffer(answer, TRUE);
1181
	    }
1182

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

1205
		/* Update current_y to account for inserted lines. */
1206
		place_the_cursor(TRUE);
1207

1208
		refresh_needed = TRUE;
1209
	    }
1210

1211
1212
1213
	    break;
	}
    }
1214
1215

    free(given);
1216
1217
}

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

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

1246
    if (origpath == NULL)
1247
	return NULL;
1248

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

1255
1256
    while (d_here == NULL && attempts < 20) {
	IGNORE_CALL_RESULT(chdir(".."));
Benno Schulenberg's avatar
Benno Schulenberg committed
1257
	d_here = getcwd(currentdir, PATH_MAX + 1);
1258
	attempts++;
1259
1260
1261
1262
    }

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

1275
    d_there = real_dir_from_tilde(origpath);
1276

1277
1278
1279
    /* 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. */
1280
    path_only = (stat(d_there, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode));
1281

1282
1283
1284
    /* If path_only is TRUE, make sure d_there ends in a slash. */
    if (path_only) {
	size_t d_there_len = strlen(d_there);
1285

1286
1287
1288
	if (d_there[d_there_len - 1] != '/') {
	    d_there = charealloc(d_there, d_there_len + 2);
	    strcat(d_there, "/");
1289
	}
1290
    }
1291

1292
1293
    /* Search for the last slash in d_there. */
    last_slash = strrchr(d_there, '/');
1294

1295
1296
1297
1298
    /* 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);
1299

1300
1301
1302
1303
1304
1305
1306
1307
	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);

1308
	/* Remove the filename portion of the answer from d_there. */
1309
	*(last_slash + 1) = '\0';
1310

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1311
	/* Go to the path specified in d_there. */
1312
1313
1314
	if (chdir(d_there) == -1) {
	    free(d_there);
	    d_there = NULL;
1315
	} else {
1316
1317
1318
	    free(d_there);

	    /* Get the full path. */
1319
1320
	    currentdir = charalloc(PATH_MAX + 1);
	    d_there = getcwd(currentdir, PATH_MAX + 1);
1321
1322
1323
1324
1325
1326
1327
1328

	    /* 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, "/");
1329
		}
1330
1331
	    /* Otherwise, make sure that we return NULL. */
	    } else {
1332
		path_only = TRUE;
1333
1334
		free(currentdir);
	    }
1335
1336
1337

	    /* Finally, go back to the path specified in d_here,
	     * where we were before.  We don't check for a chdir()
1338
	     * error, since we can do nothing if we get one. */
1339
	    IGNORE_CALL_RESULT(chdir(d_here));
1340
	}
1341
1342
1343

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

1346
1347
1348
1349
1350
1351
1352
    /* 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) +
1353
		strlen(d_there_file) + 1);
1354
1355
	strcat(d_there, d_there_file);
    }
1356

1357
    /* Free d_there_file, since we're done using it. */
1358
    free(d_there_file);
1359

1360
    return d_there;
1361
}
1362

1363
/* Return the full version of path, as returned by get_full_path().  On
1364
 * error, or if path doesn't reference a directory, or if the directory
1365
 * isn't writable, return NULL. */
1366
char *check_writable_directory(const char *path)
1367
{
1368
1369
    char *full_path = get_full_path(path);

1370
    if (full_path == NULL)
1371
	return NULL;
1372

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

    return full_path;
}

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

1394
1395
    assert(f != NULL);

1396
1397
1398
    /* 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. */
1399
    tmpdir_env = getenv("TMPDIR");
1400
    if (tmpdir_env != NULL)
1401
	full_tempdir = check_writable_directory(tmpdir_env);
1402

1403
1404
    /* If $TMPDIR is unset, empty, or not a writable directory, and
     * full_tempdir is NULL, try P_tmpdir instead. */
1405
    if (full_tempdir == NULL)
1406
	full_tempdir = check_writable_directory(P_tmpdir);
1407

1408
1409
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
1410
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1411

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1412
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1413
    strcat(full_tempdir, "nano.XXXXXX");
1414

1415
1416
1417
1418
1419
1420
1421
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

    if (fd != -1)
	*f = fdopen(fd, "r+b");
1422
1423
1424
    else {
	free(full_tempdir);
	full_tempdir = NULL;
1425
    }
1426

1427
1428
    umask(original_umask);

1429
    return full_tempdir;
1430
}
1431
1432

#ifndef DISABLE_OPERATINGDIR
1433
1434
1435
1436
1437
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1438
    if (operating_dir == NULL)
1439
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1440

1441
1442
    full_operating_dir = get_full_path(operating_dir);

1443
1444
    /* If the operating directory is inaccessible, fail. */
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1)
1445
	die(_("Invalid operating directory\n"));
1446

1447
    snuggly_fit(&full_operating_dir);
1448
1449
}

1450
1451
1452
1453
1454
/* 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)
1455
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1456
1457
1458
1459
    /* 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. */
1460

1461
    char *fullpath;
1462
    bool retval = FALSE;
1463
    const char *whereami1, *whereami2 = NULL;
1464

1465
    /* If no operating directory is set, don't bother doing anything. */
1466
    if (operating_dir == NULL)
1467
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1468

1469
    assert(full_operating_dir != NULL);
1470
1471

    fullpath = get_full_path(currpath);
1472

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

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

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

    /* Otherwise, we're still inside it. */
1497
    return retval;
1498
}
1499
1500
#endif

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

    return response;
1517
1518
}

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

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

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

1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
    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
1567

1568
1569
    if (fclose(inn) == EOF)
	retval = -1;
1570
    if (flush_out_fnc(out) == EOF)
1571
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1572

1573
1574
1575
    return retval;
}

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1618
    if (*name == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1619
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1620

1621
1622
    if (!tmp)
	titlebar(NULL);
1623

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1624
    realname = real_dir_from_tilde(name);
1625

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

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

1639
#ifndef NANO_TINY
1640
    /* Check whether the file (at the end of the symlink) exists. */
1641
    realexists = (stat(realname, &st) != -1);
1642

1643
1644
1645
1646
    /* 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. */
1647
1648
    if (openfile->current_stat == NULL && !tmp && realexists)
	stat_with_alloc(realname, &openfile->current_stat);
1649

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

1664
	/* Save the original file's access and modification times. */
1665
1666
	filetime[0].tv_sec = openfile->current_stat->st_atime;
	filetime[1].tv_sec = openfile->current_stat->st_mtime;
1667

1668
1669
1670
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1671

1672
	    if (f == NULL) {
1673
		statusline(ALERT, _("Error reading %s: %s"), realname,
1674
			strerror(errno));
1675
1676
1677
1678
		/* 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;
1679
	    }
1680
1681
	}

1682
	/* If backup_dir is set, we set backupname to
1683
1684
1685
1686
	 * 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]. */
1687
	if (backup_dir != NULL) {
1688
	    char *backuptemp = get_full_path(realname);
1689

1690
	    if (backuptemp == NULL)
1691
1692
1693
1694
1695
1696
		/* 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~. */
1697
		backuptemp = mallocstrcpy(NULL, tail(realname));
1698
	    else {
1699
1700
		size_t i = 0;

1701
1702
1703
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1704
1705
1706
		}
	    }

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

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

1743
1744
	if (ISSET(INSECURE_BACKUP))
	    backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
1745
	else
1746
1747
1748
	    backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

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

1754
	if (backup_fd < 0 || backup_file == NULL) {
1755
1756
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1757
	    free(backupname);
1758
1759
1760
1761
	    /* 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! */
1762
1763
	    goto cleanup_and_exit;
	}
1764

1765
1766
1767
1768
1769
1770
	/* 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);
1771
1772
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1773
1774
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1775
1776
1777
1778
	    free(backupname);
	    goto cleanup_and_exit;
	}

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

#ifdef DEBUG
1792
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1793
1794
#endif

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

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

1813
	fclose(backup_file);
1814
1815
	free(backupname);
    }
1816

1817
    skip_backup:
1818
#endif /* !NANO_TINY */
1819

1820
1821
    if (f_open == NULL) {
	original_umask = umask(0);
1822

1823
	/* If we create a temp file, we don't let anyone else access it.
1824
1825
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1826
	    umask(S_IRWXG | S_IRWXO);
1827
1828
	else
	    umask(original_umask);
1829
    }
1830

1831
    /* If we're prepending, copy the file to a temp file. */
1832
    if (method == PREPEND) {
1833
1834
1835
	int fd_source;
	FILE *f_source = NULL;

1836
1837
1838
1839
	if (f == NULL) {
	    f = fopen(realname, "rb");

	    if (f == NULL) {
1840
		statusline(ALERT, _("Error reading %s: %s"), realname,
1841
1842
1843
1844
1845
			strerror(errno));
		goto cleanup_and_exit;
	    }
	}

1846
	tempname = safe_tempfile(&f);
1847

1848
	if (tempname == NULL) {
1849
	    statusline(ALERT, _("Error writing temp file: %s"),
1850
			strerror(errno));
1851
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1852
	}
1853

1854
	if (f_open == NULL) {
1855
	    fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1856

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

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

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

1885
1886
	/* Set the umask back to the user's original value. */
	umask(original_umask);
1887

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

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

1899
	if (f == NULL) {
1900
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1901
			strerror(errno));
1902
1903
1904
	    close(fd);
	    goto cleanup_and_exit;
	}
1905
1906
    }

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

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

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

1915
1916
	/* Convert nulls to newlines.  data_len is the string's real
	 * length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1917
1918
	unsunder(fileptr->data, data_len);

1919
	if (size < data_len) {
1920
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1921
			strerror(errno));
1922
	    fclose(f);
1923
	    goto cleanup_and_exit;
1924
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1925

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

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

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

1958
    /* If we're prepending, open the temp file, and append it to f. */
1959
    if (method == PREPEND) {
1960
1961
1962
	int fd_source;
	FILE *f_source = NULL;

1963
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1964

1965
1966
1967
1968
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1969
	}
1970

1971
	if (f_source == NULL) {
1972
1973
	    statusline(ALERT, _("Error reading %s: %s"), tempname,
			strerror(errno));
1974
	    fclose(f);
1975
	    goto cleanup_and_exit;
1976
1977
	}

1978
	if (copy_file(f_source, f, TRUE) != 0) {
1979
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1980
			strerror(errno));
1981
	    goto cleanup_and_exit;
1982
	}
1983
1984

	unlink(tempname);
1985
    } else if (fclose(f) != 0) {
1986
	statusline(ALERT, _("Error writing %s: %s"), realname,
1987
			strerror(errno));
1988
	goto cleanup_and_exit;
1989
    }
1990

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

2001
2002
#ifndef DISABLE_COLOR
	    /* See if the applicable syntax has changed. */
2003
	    color_update();
2004
	    color_init();
2005

2006
	    newname = openfile->syntax ? openfile->syntax->name : "";
2007

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

2020
#ifndef NANO_TINY
2021
	if (!openfile->mark_set)
2022
2023
	    /* Get or update the stat info to reflect the current state. */
	    stat_with_alloc(realname, &openfile->current_stat);
2024
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2025

2026
2027
	statusline(HUSH, P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten), (unsigned long)lineswritten);
2028
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
2029
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2030
    }
2031

2032
    retval = TRUE;
2033
2034
2035

  cleanup_and_exit:
    free(realname);
2036
    free(tempname);
2037

2038
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2039
2040
}

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

2055
    /* Partition the buffer so that it contains only the marked text. */
2056
    mark_order((const filestruct **)&top, &top_x,
2057
		(const filestruct **)&bot, &bot_x, NULL);
2058
    filepart = partition_filestruct(top, top_x, bot, bot_x);
2059

2060
2061
2062
    /* 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') {
2063
	new_magicline();
2064
2065
	added_magicline = TRUE;
    }
2066

2067
    retval = write_file(name, f_open, tmp, method, TRUE);
2068

2069
2070
    /* If we added a magicline, remove it now. */
    if (added_magicline)
2071
2072
	remove_magicline();

2073
    /* Unpartition the buffer so that it contains all the text again. */
2074
    unpartition_filestruct(&filepart);
2075

2076
    if (old_modified)
2077
2078
2079
2080
	set_modified();

    return retval;
}
2081

2082
#endif /* !NANO_TINY */
2083

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

2102
    /* Display newlines in filenames as ^J. */
2103
2104
    as_an_at = FALSE;

2105
2106
2107
2108
    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
2109
2110
    }

2111
    given = mallocstrcpy(NULL,
2112
#ifndef NANO_TINY
2113
	(openfile->mark_set && !exiting) ? "" :
2114
#endif
2115
	openfile->filename);
2116
2117
2118

    while (TRUE) {
	const char *msg;
2119
#ifndef NANO_TINY
2120
2121
	const char *formatstr, *backupstr;

2122
	formatstr = (openfile->fmt == DOS_FILE) ? _(" [DOS Format]") :
2123
			(openfile->fmt == MAC_FILE) ? _(" [Mac Format]") : "";
2124

2125
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2126

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

2143
2144
	present_path = mallocstrcpy(present_path, "./");

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

2160
	if (i < 0) {
2161
	    statusbar(_("Cancelled"));
2162
2163
	    break;
	} else {
2164
2165
	    functionptrtype func = func_from_key(&i);

2166
	    /* Upon request, abandon the buffer. */
2167
	    if (func == discard_buffer) {
2168
2169
		free(given);
		return 2;
2170
2171
	    }

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

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

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

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

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

2214
#ifndef DISABLE_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2215
2216
2217
2218
2219
2220
2221
	    /* If the current file has been modified, we've pressed
	     * Ctrl-X at the edit window to exit, we've pressed "y" at
	     * the "Save modified buffer" prompt to save, we've entered
	     * "zzy" as the filename to save under (hence "xyzzy"), and
	     * this is the first time we've done this, show an Easter
	     * egg.  Display the credits. */
	    if (!did_credits && exiting && !ISSET(TEMP_FILE) &&
2222
			strcasecmp(answer, "zzy") == 0) {
2223
		do_credits();
2224
		did_credits = TRUE;
2225
2226
		break;
	    }
2227
#endif
2228

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

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

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

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

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

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

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

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

2293
2294
		    warn_and_shortly_pause(_("File on disk has changed"));

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

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

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

2317
    free(given);
2318

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

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

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

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

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

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

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

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

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

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

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

2385
    return retval;
2386
2387
}

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

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

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

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

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

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

    dirptr = real_dir_from_tilde(buf);

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

2437
    free(dirptr);
2438

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

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

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

2470
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2471

2472
    *num_matches = 0;
2473

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

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

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

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

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

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

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

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

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

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

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

    filenamelen = strlen(filename);

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

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

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

2566
2567
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2568
2569
2570
2571
2572
2573
2574
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(tmp, TRUE))
		skip_match = TRUE;
#endif

	    /* ...or unless the match isn't a directory and allow_files
	     * isn't set, in which case just go to the next match. */
2575
	    if (!allow_files && !is_dir(tmp))
2576
2577
2578
		skip_match = TRUE;

	    free(tmp);
2579

2580
	    if (skip_match)
2581
		continue;
2582

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

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

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

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

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

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

2611
2612
    /* If the word starts with `~' and there is no slash in the word,
     * then try completing this word as a username. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2613
    if (*place > 0 && *buf == '~') {
2614
	const char *slash = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2615

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

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

2624
2625
2626
    buf_len = strlen(buf);

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

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

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

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

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

2650
	    common_len += len1;
2651
	}
2652

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

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

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

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

2665
	assert(common_len >= *place);
2666

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2741
    free_chararray(matches, num_matches);
2742

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

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

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

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

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

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

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

2778
2779
2780
2781
2782
    return newstr;
}

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

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

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

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

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

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

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

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

    free(nanodir);
    return ret;
2838
2839
}

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

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

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

2864
	if (hist == NULL) {
2865
	    if (errno != ENOENT) {
2866
2867
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2868
		history_error(N_("Error reading %s: %s"), nanohist,
2869
			strerror(errno));
2870
	    }
2871
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2872
2873
2874
	    /* Load a history list (first the search history, then the
	     * replace history) from the oldest entry to the newest.
	     * Assume the last history entry is a blank line. */
2875
	    filestruct **history = &search_history;
2876
	    char *line = NULL;
2877
	    size_t buf_len = 0;
2878
2879
	    ssize_t read;

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

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

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

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

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

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

2918
    return TRUE;
2919
2920
}

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

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

2931
2932
2933
2934
    nanohist = histfilename();

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

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

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

2948
2949
	    fclose(hist);
	}
2950

2951
2952
2953
	free(nanohist);
    }
}
2954

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

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

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

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

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

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

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

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

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

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

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

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

3032
    theone = posptr;
3033

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

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

    free(fullpath);
}

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

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

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

3075
    free(fullpath);
3076
3077

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

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

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

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

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

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

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

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

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

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

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

	    record_ptr = newrecord;
3140
3141

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

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

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