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

22
#include "proto.h"
23

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

33
34
#define LOCKBUFSIZE 8192

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

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

57
    free(namecopy);
58

59
    return validity;
60
61
}

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

67
    if (openfile == NULL) {
68
	/* Make the first open file the only element in the list. */
69
70
	newnode->prev = newnode;
	newnode->next = newnode;
71
    } else {
72
73
74
75
76
77
	/* 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;

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

82
83
84
    /* Make the new buffer the current one, and start initializing it. */
    openfile = newnode;

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

87
    initialize_buffer_text();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
88

89
    openfile->placewewant = 0;
90
    openfile->current_y = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
91

92
    openfile->modified = FALSE;
93
#ifndef NANO_TINY
94
    openfile->mark_set = FALSE;
95
96
    openfile->mark_begin = NULL;
    openfile->mark_begin_x = 0;
97
    openfile->kind_of_mark = SOFTMARK;
98

99
    openfile->fmt = NIX_FILE;
100

101
102
    openfile->undotop = NULL;
    openfile->current_undo = NULL;
103
    openfile->last_action = OTHER;
104
105

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

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

120
121
122
123
    openfile->filebot = openfile->fileage;
    openfile->edittop = openfile->fileage;
    openfile->current = openfile->fileage;

124
    openfile->firstcolumn = 0;
125
    openfile->current_x = 0;
126
    openfile->totsize = 0;
127
128
}

129
130
131
132
133
134
135
136
137
138
139
/* 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
140
    if (!ISSET(LOCKING) || openfile->filename[0] == '\0')
141
142
	return;

143
    if (openfile->lock_filename != NULL) {
144
	char *fullname = get_full_path(openfile->filename);
145

146
147
148
149
150
151
	write_lockfile(openfile->lock_filename, fullname, TRUE);
	free(fullname);
    }
#endif
}

152
#ifndef NANO_TINY
153
154
155
156
157
158
159
160
161
/* 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
 *
162
 * Returns 1 on success, and 0 on failure (but continue anyway). */
163
164
int write_lockfile(const char *lockfilename, const char *origfilename, bool modified)
{
165
#ifdef HAVE_PWD_H
166
167
168
169
170
    int cflags, fd;
    FILE *filestream;
    pid_t mypid;
    uid_t myuid;
    struct passwd *mypwuid;
171
    struct stat fileinfo;
172
173
    char *lockdata = charalloc(1024);
    char myhostname[32];
174
175
    size_t lockdatalen = 1024;
    size_t wroteamt;
176

177
    mypid = getpid();
178
    myuid = geteuid();
179
180

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

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

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

    if (ISSET(INSECURE_BACKUP))
204
	cflags = O_WRONLY | O_CREAT | O_APPEND;
205
    else
206
	cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;
207

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

217
    /* Try to associate a stream with the now open lockfile. */
218
219
    filestream = fdopen(fd, "wb");

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

226
    /* This is the lock data we will store:
227
228
229
230
     *
     * byte 0        - 0x62
     * byte 1        - 0x30
     * bytes 2-12    - program name which created the lock
231
     * bytes 24-27   - PID (little endian) of creator process
232
233
234
235
236
237
238
239
240
241
     * 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. */
242
    memset(lockdata, 0, lockdatalen);
243
244
245
    lockdata[0] = 0x62;
    lockdata[1] = 0x30;
    lockdata[24] = mypid % 256;
246
247
248
    lockdata[25] = (mypid / 256) % 256;
    lockdata[26] = (mypid / (256 * 256)) % 256;
    lockdata[27] = mypid / (256 * 256 * 256);
249
    snprintf(&lockdata[2], 11, "nano %s", VERSION);
250
251
    strncpy(&lockdata[28], mypwuid->pw_name, 16);
    strncpy(&lockdata[68], myhostname, 31);
252
253
    if (origfilename != NULL)
	strncpy(&lockdata[108], origfilename, 768);
254
    if (modified == TRUE)
255
	lockdata[1007] = 0x55;
256
257
258

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

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

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

274
    openfile->lock_filename = (char *) lockfilename;
275

Benno Schulenberg's avatar
Benno Schulenberg committed
276
    free(lockdata);
277
    return 1;
Benno Schulenberg's avatar
Benno Schulenberg committed
278

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

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

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

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

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

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

336
337
	close(lockfd);

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

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

351
352
353
	pidstring = charalloc(11);
	sprintf (pidstring, "%u", (unsigned int)lockpid);

354
#ifdef DEBUG
355
356
357
	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);
358
#endif
359
	/* TRANSLATORS: The second %s is the name of the user, the third that of the editor. */
360
361
362
	question = _("File %s is being edited (by %s with %s, PID %s); continue?");
	room = COLS - strlenpt(question) + 7 - strlenpt(lockuser) -
				strlenpt(lockprog) - strlenpt(pidstring);
363
	if (room < 4)
364
365
366
367
368
369
370
371
372
373
	    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);
374

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

382
	response = do_yesno_prompt(FALSE, promptstr);
383
384
	free(promptstr);

385
	if (response < 1) {
386
	    blank_statusbar();
387
	    goto free_the_name;
388
	}
389
    }
390
391
392
393

    retval = write_lockfile(lockfilename, filename, FALSE);

  free_the_name:
394
    free(namecopy);
395
396
397
398
    if (retval < 1)
	free(lockfilename);

    return retval;
399
}
400
401
402
403
404
405
406
407
408
409
410
411
412
413

/* 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;
    }
}
414
#endif /* !NANO_TINY */
415

416
417
418
/* 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. */
419
bool open_buffer(const char *filename, bool undoable)
420
{
421
422
    bool new_buffer = (openfile == NULL || ISSET(MULTIBUFFER));
	/* Whether we load into the current buffer or a new one. */
423
424
    char *realname;
	/* The filename after tilde expansion. */
425
426
427
428
    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. */
429

430
431
432
    /* Display newlines in filenames as ^J. */
    as_an_at = FALSE;

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

441
442
    realname = real_dir_from_tilde(filename);

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

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

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

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

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

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

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

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

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

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

525
    /* Open the file quietly. */
526
527
528
529
530
    descriptor = open_file(filename, FALSE, TRUE, &f);

    /* If opening failed, forget it. */
    if (descriptor < 0)
	return;
531
532
533
534
535

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

536
537
    /* Insert the processed file into its place. */
    read_file(f, descriptor, filename, FALSE, TRUE);
538
539
540

    /* Put current at a place that is certain to exist. */
    openfile->current = openfile->fileage;
541
}
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573

#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);
}
574
#endif /* !NANO_TINY */
575
576
#endif /* !DISABLE_SPELLER */

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

584
#ifndef DISABLE_COLOR
585
586
587
588
589
    /* 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();
590

591
592
    have_palette = FALSE;
#endif
593
    refresh_needed = TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
594
595
}

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

607
608
    /* Switch to the next or previous file buffer. */
    openfile = to_next ? openfile->next : openfile->prev;
609
610

#ifdef DEBUG
611
    fprintf(stderr, "filename is %s\n", openfile->filename);
612
613
#endif

614
615
616
617
618
619
620
621
#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

622
623
    /* Update titlebar and multiline info to match the current buffer. */
    prepare_for_display();
624

625
626
627
628
629
630
    if (inhelp)
	return;

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

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

#ifdef DEBUG
637
    dump_filestruct(openfile->current);
638
#endif
Chris Allegretta's avatar
Chris Allegretta committed
639
640
}

641
/* Switch to the previous entry in the openfile filebuffer. */
642
void switch_to_prev_buffer_void(void)
643
{
644
    switch_to_prevnext_buffer(FALSE);
645
}
646

647
/* Switch to the next entry in the openfile filebuffer. */
648
void switch_to_next_buffer_void(void)
649
{
650
    switch_to_prevnext_buffer(TRUE);
651
}
652

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

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

668
    /* Switch to the next file buffer. */
669
    switch_to_prevnext_buffer(TRUE);
670

671
    /* Close the file buffer we had open before. */
672
    unlink_opennode(openfile->prev);
673

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

678
    return TRUE;
679
}
680
#endif /* ENABLE_MULTIBUFFER */
681

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

    if (ISSET(VIEW_MODE))
	return TRUE;

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

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

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

713
    close(fd);
714
    free(full_filename);
715
716

    return result;
717
718
}

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

726
    return mallocstrcpy(NULL, buf);
727
}
728

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

765
    buf = charalloc(bufx);
766

767
768
769
#ifndef NANO_TINY
    if (undoable)
	add_undo(INSERT);
770
771

    if (ISSET(SOFTWRAP))
772
	was_leftedge = leftedge_for(xplustabs(), openfile->current);
773
774
#endif

775
776
777
778
779
    /* Create an empty buffer. */
    topline = make_new_node(NULL);
    bottomline = topline;

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

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

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

#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
845
846
847
848
849
850
    }

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

856
857
858
859
860
861
862
    /* 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;

863
#ifndef NANO_TINY
864
865
866
867
868
869
	/* 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;
870

871
872
	    /* Strip the carriage return. */
	    buf[--len] = '\0';
873

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

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

889
    free(buf);
890

891
892
    /* Insert the just read buffer into the current one. */
    ingraft_buffer(topline);
893

894
    /* Set the desired x position at the end of what was inserted. */
895
896
    openfile->placewewant = xplustabs();

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

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

925
    /* If we inserted less than a screenful, don't center the cursor. */
926
    if (undoable && less_than_a_screenful(was_lineno, was_leftedge))
927
928
	focusing = FALSE;

929
#ifndef NANO_TINY
930
931
932
    if (undoable)
	update_undo(INSERT);

933
934
    if (ISSET(MAKE_IT_UNIX))
	openfile->fmt = NIX_FILE;
935
#endif
936
}
Chris Allegretta's avatar
Chris Allegretta committed
937

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

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

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

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

965
	statusline(ALERT, _("File \"%s\" not found"), filename);
966
	free(full_filename);
967
968
	return -1;
    }
969

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

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

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

996
997
    free(full_filename);

998
999
1000
    return fd;
}

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

1011
    wholenamelen = strlen(name) + strlen(suffix);
1012

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

1018
1019
    while (TRUE) {
	struct stat fs;
Chris Allegretta's avatar
Chris Allegretta committed
1020

1021
1022
	if (stat(buf, &fs) == -1)
	    return buf;
1023
1024
1025

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

1028
	sprintf(buf + wholenamelen, ".%lu", i);
1029
    }
Chris Allegretta's avatar
Chris Allegretta committed
1030

1031
1032
    /* There is no possible save file: blank out the filename. */
    *buf = '\0';
1033

1034
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
1035
1036
}

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

1049
    /* Display newlines in filenames as ^J. */
1050
1051
    as_an_at = FALSE;

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

1073
1074
	present_path = mallocstrcpy(present_path, "./");

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

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

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

1122
		/* If no file was chosen, go back to the prompt. */
1123
		if (chosen == NULL)
1124
		    continue;
1125

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

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

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

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

1162
		/* Save the file specified in answer in the current buffer. */
1163
		open_buffer(answer, TRUE);
1164
	    }
1165

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

1188
1189
		/* Set the target row for the cursor when pushed offscreen. */
		openfile->current_y = editwinrows - 1;
1190

1191
		refresh_needed = TRUE;
1192
	    }
1193

1194
1195
1196
	    break;
	}
    }
1197
1198

    free(given);
1199
1200
}

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

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

1229
    if (origpath == NULL)
1230
	return NULL;
1231

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

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

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

1258
    d_there = real_dir_from_tilde(origpath);
1259

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

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

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

1275
1276
    /* Search for the last slash in d_there. */
    last_slash = strrchr(d_there, '/');
1277

1278
1279
1280
1281
    /* 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);
1282

1283
1284
1285
1286
1287
1288
1289
1290
	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);

1291
	/* Remove the filename portion of the answer from d_there. */
1292
	*(last_slash + 1) = '\0';
1293

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

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

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

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

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

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

1340
    /* Free d_there_file, since we're done using it. */
1341
    free(d_there_file);
1342

1343
    return d_there;
1344
}
1345

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

1353
    if (full_path == NULL)
1354
	return NULL;
1355

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

    return full_path;
}

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

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

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

1389
1390
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
1391
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1392

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1393
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1394
    strcat(full_tempdir, "nano.XXXXXX");
1395

1396
1397
1398
1399
1400
1401
1402
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

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

1408
1409
    umask(original_umask);

1410
    return full_tempdir;
1411
}
1412
1413

#ifndef DISABLE_OPERATINGDIR
1414
/* Change to the specified operating directory, when it's valid. */
1415
1416
1417
1418
void init_operating_dir(void)
{
    full_operating_dir = get_full_path(operating_dir);

1419
1420
    /* If the operating directory is inaccessible, fail. */
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1)
1421
	die(_("Invalid operating directory\n"));
1422

1423
    snuggly_fit(&full_operating_dir);
1424
1425
}

1426
1427
1428
1429
1430
/* 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)
1431
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1432
1433
1434
1435
    /* 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. */
1436

1437
    char *fullpath;
1438
    bool retval = FALSE;
1439
    const char *whereami1, *whereami2 = NULL;
1440

1441
    /* If no operating directory is set, don't bother doing anything. */
1442
    if (operating_dir == NULL)
1443
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1444

1445
    fullpath = get_full_path(currpath);
1446

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

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

1461
    /* If both searches failed, we're outside the operating directory.
1462
     * Otherwise, check the search results.  If the full operating
1463
1464
1465
     * 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. */
1466
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1467
1468
	retval = TRUE;
    free(fullpath);
1469
1470

    /* Otherwise, we're still inside it. */
1471
    return retval;
1472
}
1473
1474
#endif

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

    return response;
1491
1492
}

1493
/* Transform the specified backup directory to an absolute path. */
1494
1495
void init_backup_dir(void)
{
1496
    char *full_backup_dir = get_full_path(backup_dir);
1497

1498
1499
    /* When we can't get an absolute path, or it's not a directory,
     * cancel the making of backups. */
1500
    if (full_backup_dir == NULL ||
1501
		full_backup_dir[strlen(full_backup_dir) - 1] != '/') {
1502
1503
1504
1505
1506
1507
	free(full_backup_dir);
	free(backup_dir);
	backup_dir = NULL;
    } else {
	free(backup_dir);
	backup_dir = full_backup_dir;
1508
	snuggly_fit(&backup_dir);
1509
1510
    }
}
1511
#endif /* !NANO_TINY */
1512

1513
/* Read from inn, write to out.  We assume inn is opened for reading,
1514
 * and out for writing.  We return 0 on success, -1 on read error, or -2
1515
1516
1517
 * 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)
1518
{
1519
    int retval = 0;
1520
    char buf[BUFSIZ];
1521
    size_t charsread;
1522
    int (*flush_out_fnc)(FILE *) = (close_out) ? fclose : fflush;
1523

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

1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
    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
1537

1538
1539
    if (fclose(inn) == EOF)
	retval = -1;
1540
    if (flush_out_fnc(out) == EOF)
1541
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1542

1543
1544
1545
    return retval;
}

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1588
    if (*name == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1589
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1590

1591
1592
    if (!tmp)
	titlebar(NULL);
1593

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1594
    realname = real_dir_from_tilde(name);
1595

1596
#ifndef DISABLE_OPERATINGDIR
1597
    /* If we're writing a temporary file, we're probably going outside
1598
     * the operating directory, so skip the operating directory test. */
1599
    if (!tmp && check_operating_dir(realname, FALSE)) {
1600
	statusline(ALERT, _("Can't write outside of %s"), full_operating_dir);
1601
	goto cleanup_and_exit;
1602
1603
1604
    }
#endif

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

1609
#ifndef NANO_TINY
1610
    /* Check whether the file (at the end of the symlink) exists. */
1611
    realexists = (stat(realname, &st) != -1);
1612

1613
1614
1615
1616
    /* 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. */
1617
1618
    if (openfile->current_stat == NULL && !tmp && realexists)
	stat_with_alloc(realname, &openfile->current_stat);
1619

1620
    /* We backup only if the backup toggle is set, the file isn't
1621
1622
1623
1624
     * 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. */
1625
    if (ISSET(BACKUP_FILE) && !tmp && realexists && openfile->current_stat &&
1626
		(method != OVERWRITE || openfile->mark_set ||
1627
		openfile->current_stat->st_mtime == st.st_mtime)) {
1628
	int backup_fd;
1629
	FILE *backup_file;
1630
	char *backupname;
1631
	static struct timespec filetime[2];
1632
	int backup_cflags;
1633

1634
	/* Save the original file's access and modification times. */
1635
1636
	filetime[0].tv_sec = openfile->current_stat->st_atime;
	filetime[1].tv_sec = openfile->current_stat->st_mtime;
1637

1638
1639
1640
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1641

1642
	    if (f == NULL) {
1643
		statusline(ALERT, _("Error reading %s: %s"), realname,
1644
			strerror(errno));
1645
1646
1647
1648
		/* 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;
1649
	    }
1650
1651
	}

1652
	/* If backup_dir is set, we set backupname to
1653
1654
1655
1656
	 * 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]. */
1657
	if (backup_dir != NULL) {
1658
	    char *backuptemp = get_full_path(realname);
1659

1660
	    if (backuptemp == NULL)
1661
1662
1663
1664
1665
1666
		/* 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~. */
1667
		backuptemp = mallocstrcpy(NULL, tail(realname));
1668
	    else {
1669
1670
		size_t i = 0;

1671
1672
1673
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1674
1675
1676
		}
	    }

1677
	    backupname = charalloc(strlen(backup_dir) + strlen(backuptemp) + 1);
1678
1679
1680
	    sprintf(backupname, "%s%s", backup_dir, backuptemp);
	    free(backuptemp);
	    backuptemp = get_next_filename(backupname, "~");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1681
	    if (*backuptemp == '\0') {
1682
1683
		statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, _("Too many backup files?"));
1684
1685
		free(backuptemp);
		free(backupname);
1686
1687
1688
1689
1690
		/* 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! */
1691
		goto cleanup_and_exit;
1692
1693
1694
1695
	    } else {
		free(backupname);
		backupname = backuptemp;
	    }
1696
1697
1698
1699
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1700

1701
	/* First, unlink any existing backups.  Next, open the backup
1702
1703
	 * file with O_CREAT and O_EXCL.  If it succeeds, we have a file
	 * descriptor to a new backup file. */
1704
	if (unlink(backupname) < 0 && errno != ENOENT && !ISSET(INSECURE_BACKUP)) {
1705
1706
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1707
1708
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1709
1710
1711
1712
	    free(backupname);
	    goto cleanup_and_exit;
	}

1713
1714
	if (ISSET(INSECURE_BACKUP))
	    backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
1715
	else
1716
1717
1718
	    backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

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

1724
	if (backup_fd < 0 || backup_file == NULL) {
1725
1726
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1727
	    free(backupname);
1728
1729
1730
1731
	    /* 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! */
1732
1733
	    goto cleanup_and_exit;
	}
1734

1735
1736
1737
1738
1739
1740
	/* 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);
1741
1742
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1743
1744
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1745
1746
1747
1748
	    free(backupname);
	    goto cleanup_and_exit;
	}

1749
1750
1751
1752
	/* Set the backup's mode bits. */
	if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1 &&
			!ISSET(INSECURE_BACKUP)) {
	    fclose(backup_file);
1753
1754
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1755
1756
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1757
	    free(backupname);
1758
	    goto cleanup_and_exit;
1759
1760
1761
	}

#ifdef DEBUG
1762
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1763
1764
#endif

1765
	/* Copy the file. */
1766
1767
	if (copy_file(f, backup_file, FALSE) != 0) {
	    fclose(backup_file);
1768
	    statusline(ALERT, _("Error reading %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1769
			strerror(errno));
1770
1771
1772
	    goto cleanup_and_exit;
	}

1773
	/* And set the backup's timestamps. */
1774
1775
	if (futimens(backup_fd, filetime) == -1 && !ISSET(INSECURE_BACKUP)) {
	    fclose(backup_file);
1776
1777
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1778
1779
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1780
	    goto cleanup_and_exit;
1781
	}
1782

1783
	fclose(backup_file);
1784
1785
	free(backupname);
    }
1786

1787
    skip_backup:
1788
#endif /* !NANO_TINY */
1789

1790
1791
    if (f_open == NULL) {
	original_umask = umask(0);
1792

1793
	/* If we create a temp file, we don't let anyone else access it.
1794
1795
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1796
	    umask(S_IRWXG | S_IRWXO);
1797
1798
	else
	    umask(original_umask);
1799
    }
1800

1801
    /* If we're prepending, copy the file to a temp file. */
1802
    if (method == PREPEND) {
1803
1804
1805
	int fd_source;
	FILE *f_source = NULL;

1806
1807
1808
1809
	if (f == NULL) {
	    f = fopen(realname, "rb");

	    if (f == NULL) {
1810
		statusline(ALERT, _("Error reading %s: %s"), realname,
1811
1812
1813
1814
1815
			strerror(errno));
		goto cleanup_and_exit;
	    }
	}

1816
	tempname = safe_tempfile(&f);
1817

1818
	if (tempname == NULL) {
1819
	    statusline(ALERT, _("Error writing temp file: %s"),
1820
			strerror(errno));
1821
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1822
	}
1823

1824
	if (f_open == NULL) {
1825
	    fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1826

1827
1828
1829
	    if (fd_source != -1) {
		f_source = fdopen(fd_source, "rb");
		if (f_source == NULL) {
1830
		    statusline(ALERT, _("Error reading %s: %s"), realname,
1831
				strerror(errno));
1832
1833
1834
1835
1836
1837
		    close(fd_source);
		    fclose(f);
		    unlink(tempname);
		    goto cleanup_and_exit;
		}
	    }
1838
1839
	}

1840
	if (f_source == NULL || copy_file(f_source, f, TRUE) != 0) {
1841
	    statusline(ALERT, _("Error writing temp file: %s"),
1842
			strerror(errno));
1843
	    unlink(tempname);
1844
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1845
1846
1847
	}
    }

1848
1849
1850
    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*. */
1851
	fd = open(realname, O_WRONLY | O_CREAT | ((method == APPEND) ?
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1852
1853
		O_APPEND : (tmp ? O_EXCL : O_TRUNC)), S_IRUSR |
		S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1854

1855
1856
	/* Set the umask back to the user's original value. */
	umask(original_umask);
1857

1858
1859
	/* If we couldn't open the file, give up. */
	if (fd == -1) {
1860
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1861
			strerror(errno));
1862
1863
1864
1865
	    if (tempname != NULL)
		unlink(tempname);
	    goto cleanup_and_exit;
	}
1866

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

1869
	if (f == NULL) {
1870
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1871
			strerror(errno));
1872
1873
1874
	    close(fd);
	    goto cleanup_and_exit;
	}
1875
1876
    }

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

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

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

1885
1886
	/* Convert nulls to newlines.  data_len is the string's real
	 * length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1887
1888
	unsunder(fileptr->data, data_len);

1889
	if (size < data_len) {
1890
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1891
			strerror(errno));
1892
	    fclose(f);
1893
	    goto cleanup_and_exit;
1894
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1895

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1896
	/* If we're on the last line of the file, don't write a newline
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1897
1898
1899
1900
1901
1902
1903
	 * 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 {
1904
#ifndef NANO_TINY
1905
	    if (openfile->fmt == DOS_FILE || openfile->fmt == MAC_FILE) {
1906
		if (putc('\r', f) == EOF) {
1907
		    statusline(ALERT, _("Error writing %s: %s"), realname,
1908
				strerror(errno));
1909
1910
1911
		    fclose(f);
		    goto cleanup_and_exit;
		}
1912
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1913

1914
	    if (openfile->fmt != MAC_FILE)
1915
#endif
1916
		if (putc('\n', f) == EOF) {
1917
		    statusline(ALERT, _("Error writing %s: %s"), realname,
1918
				strerror(errno));
1919
1920
1921
1922
		    fclose(f);
		    goto cleanup_and_exit;
		}
	}
Chris Allegretta's avatar
Chris Allegretta committed
1923
1924
1925
1926
1927

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

1928
    /* If we're prepending, open the temp file, and append it to f. */
1929
    if (method == PREPEND) {
1930
1931
1932
	int fd_source;
	FILE *f_source = NULL;

1933
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1934

1935
1936
1937
1938
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1939
	}
1940

1941
	if (f_source == NULL) {
1942
1943
	    statusline(ALERT, _("Error reading %s: %s"), tempname,
			strerror(errno));
1944
	    fclose(f);
1945
	    goto cleanup_and_exit;
1946
1947
	}

1948
	if (copy_file(f_source, f, TRUE) != 0) {
1949
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1950
			strerror(errno));
1951
	    goto cleanup_and_exit;
1952
	}
1953
1954

	unlink(tempname);
1955
    } else if (fclose(f) != 0) {
1956
	statusline(ALERT, _("Error writing %s: %s"), realname,
1957
			strerror(errno));
1958
	goto cleanup_and_exit;
1959
    }
1960

1961
    if (method == OVERWRITE && !tmp) {
1962
1963
	/* If we must set the filename, and it changed, adjust things. */
	if (!nonamechange && strcmp(openfile->filename, realname) != 0) {
1964
#ifndef DISABLE_COLOR
1965
	    char *newname;
1966
	    char *oldname = openfile->syntax ? openfile->syntax->name : "";
1967
1968
1969
	    filestruct *line = openfile->fileage;
#endif
	    openfile->filename = mallocstrcpy(openfile->filename, realname);
1970

1971
1972
#ifndef DISABLE_COLOR
	    /* See if the applicable syntax has changed. */
1973
	    color_update();
1974
	    color_init();
1975

1976
	    newname = openfile->syntax ? openfile->syntax->name : "";
1977

1978
	    /* If the syntax changed, discard and recompute the multidata. */
1979
	    if (strcmp(oldname, newname) != 0) {
1980
1981
1982
1983
1984
		for (; line != NULL; line = line->next) {
		    free(line->multidata);
		    line->multidata = NULL;
		}
		precalc_multicolorinfo();
1985
		refresh_needed = TRUE;
1986
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1987
1988
#endif
	}
1989

1990
#ifndef NANO_TINY
1991
	if (!openfile->mark_set)
1992
1993
	    /* Get or update the stat info to reflect the current state. */
	    stat_with_alloc(realname, &openfile->current_stat);
1994
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1995

1996
1997
	statusline(HUSH, P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten), (unsigned long)lineswritten);
1998
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1999
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2000
    }
2001

2002
    retval = TRUE;
2003
2004
2005

  cleanup_and_exit:
    free(realname);
2006
    free(tempname);
2007

2008
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2009
2010
}

2011
#ifndef NANO_TINY
2012
2013
/* Write a marked selection from a file out to disk.  Return TRUE on
 * success or FALSE on error. */
2014
bool write_marked_file(const char *name, FILE *f_open, bool tmp,
2015
	kind_of_writing_type method)
2016
{
2017
    bool retval;
2018
    bool old_modified = openfile->modified;
2019
	/* Save the status, because write_file() unsets the modified flag. */
2020
    bool added_magicline = FALSE;
2021
2022
2023
2024
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

2025
    /* Partition the buffer so that it contains only the marked text. */
2026
    mark_order((const filestruct **)&top, &top_x,
2027
		(const filestruct **)&bot, &bot_x, NULL);
2028
    filepart = partition_filestruct(top, top_x, bot, bot_x);
2029

2030
2031
2032
    /* 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') {
2033
	new_magicline();
2034
2035
	added_magicline = TRUE;
    }
2036

2037
    retval = write_file(name, f_open, tmp, method, TRUE);
2038

2039
2040
    /* If we added a magicline, remove it now. */
    if (added_magicline)
2041
2042
	remove_magicline();

2043
    /* Unpartition the buffer so that it contains all the text again. */
2044
    unpartition_filestruct(&filepart);
2045

2046
    if (old_modified)
2047
2048
2049
2050
	set_modified();

    return retval;
}
2051

2052
#endif /* !NANO_TINY */
2053

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

2072
    /* Display newlines in filenames as ^J. */
2073
2074
    as_an_at = FALSE;

2075
2076
2077
2078
    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
2079
2080
    }

2081
    given = mallocstrcpy(NULL,
2082
#ifndef NANO_TINY
2083
	(openfile->mark_set && !exiting) ? "" :
2084
#endif
2085
	openfile->filename);
2086
2087
2088

    while (TRUE) {
	const char *msg;
2089
#ifndef NANO_TINY
2090
2091
	const char *formatstr, *backupstr;

2092
	formatstr = (openfile->fmt == DOS_FILE) ? _(" [DOS Format]") :
2093
			(openfile->fmt == MAC_FILE) ? _(" [Mac Format]") : "";
2094

2095
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2096

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

2113
2114
	present_path = mallocstrcpy(present_path, "./");

2115
2116
	/* If we're using restricted mode, and the filename isn't blank,
	 * disable tab completion. */
2117
	i = do_prompt(!ISSET(RESTRICTED) || openfile->filename[0] == '\0',
2118
		TRUE, MWRITEFILE, given,
2119
#ifndef DISABLE_HISTORIES
2120
2121
		NULL,
#endif
2122
		edit_refresh, "%s%s%s", msg,
2123
2124
#ifndef NANO_TINY
		formatstr, backupstr
2125
#else
2126
		"", ""
2127
2128
2129
#endif
		);

2130
	if (i < 0) {
2131
	    statusbar(_("Cancelled"));
2132
2133
	    break;
	} else {
2134
2135
	    functionptrtype func = func_from_key(&i);

2136
	    /* Upon request, abandon the buffer. */
2137
	    if (func == discard_buffer) {
2138
2139
		free(given);
		return 2;
2140
2141
	    }

2142
	    given = mallocstrcpy(given, answer);
Chris Allegretta's avatar
Chris Allegretta committed
2143

2144
#ifdef ENABLE_BROWSER
2145
	    if (func == to_files_void) {
2146
		char *chosen = do_browse_from(answer);
2147

2148
		if (chosen == NULL)
2149
		    continue;
2150

2151
		/* We have a file now.  Indicate this. */
2152
		free(answer);
2153
		answer = chosen;
2154
	    } else
2155
#endif
2156
#ifndef NANO_TINY
2157
	    if (func == dos_format_void) {
2158
2159
		openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE :
			DOS_FILE;
2160
		continue;
2161
	    } else if (func == mac_format_void) {
2162
2163
		openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE :
			MAC_FILE;
2164
		continue;
2165
	    } else if (func == backup_file_void) {
2166
2167
		TOGGLE(BACKUP_FILE);
		continue;
2168
	    } else if (func == prepend_void) {
2169
		method = (method == PREPEND) ? OVERWRITE : PREPEND;
2170
		continue;
2171
	    } else if (func == append_void) {
2172
		method = (method == APPEND) ? OVERWRITE : APPEND;
2173
		continue;
2174
2175
2176
	    } else
#endif /* !NANO_TINY */
	    if (func == do_help_void) {
2177
		continue;
2178
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2179

Chris Allegretta's avatar
Chris Allegretta committed
2180
#ifdef DEBUG
2181
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
2182
#endif
2183

2184
#ifndef DISABLE_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2185
2186
2187
2188
2189
2190
2191
	    /* 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) &&
2192
			strcasecmp(answer, "zzy") == 0) {
2193
		do_credits();
2194
		did_credits = TRUE;
2195
2196
		break;
	    }
2197
#endif
2198

2199
	    if (method == OVERWRITE) {
2200
		bool name_exists, do_warning;
2201
		char *full_answer, *full_filename;
2202
2203
		struct stat st;

2204
2205
		full_answer = get_full_path(answer);
		full_filename = get_full_path(openfile->filename);
2206
2207
		name_exists = (stat((full_answer == NULL) ?
				answer : full_answer, &st) != -1);
2208
2209
2210
2211
		if (openfile->filename[0] == '\0')
		    do_warning = name_exists;
		else
		    do_warning = (strcmp((full_answer == NULL) ?
2212
2213
				answer : full_answer, (full_filename == NULL) ?
				openfile->filename : full_filename) != 0);
2214

2215
2216
		free(full_filename);
		free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2217

2218
		if (do_warning) {
2219
2220
2221
		    /* 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. */
2222
		    if (ISSET(RESTRICTED)) {
2223
			/* TRANSLATORS: Restricted mode forbids overwriting. */
2224
2225
			warn_and_shortly_pause(_("File exists -- "
					"cannot overwrite"));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2226
			continue;
2227
		    }
2228

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

2263
2264
		    warn_and_shortly_pause(_("File on disk has changed"));

2265
2266
		    if (do_yesno_prompt(FALSE, _("File was modified since "
				"you opened it; continue saving? ")) < 1)
2267
2268
			continue;
		}
2269
#endif
2270
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2271

2272
	    /* Here's where we allow the selected text to be written to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2273
2274
2275
	     * 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. */
2276
#ifndef NANO_TINY
2277
	    if (openfile->mark_set && !exiting && !ISSET(RESTRICTED))
2278
		result = write_marked_file(answer, NULL, FALSE, method);
2279
	    else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2280
#endif
2281
		result = write_file(answer, NULL, FALSE, method, FALSE);
2282

2283
2284
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2285
    }
2286

2287
    free(given);
2288

2289
    return result ? 1 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
2290
2291
}

2292
/* Write the current buffer to disk, or discard it. */
2293
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
2294
{
2295
2296
2297
    /* 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
2298
}
Chris Allegretta's avatar
Chris Allegretta committed
2299

2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
#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

2311
2312
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
2313
char *real_dir_from_tilde(const char *buf)
2314
{
2315
    char *retval;
2316

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2317
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2318
	size_t i = 1;
2319
	char *tilde_dir;
2320

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

2325
2326
2327
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
2328
	    tilde_dir = mallocstrcpy(NULL, homedir);
2329
	} else {
2330
#ifdef HAVE_PWD_H
2331
2332
	    const struct passwd *userdata;

2333
2334
2335
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

2336
2337
	    do {
		userdata = getpwent();
2338
2339
	    } while (userdata != NULL &&
			strcmp(userdata->pw_name, tilde_dir + 1) != 0);
2340
	    endpwent();
2341
	    if (userdata != NULL)
2342
		tilde_dir = mallocstrcpy(tilde_dir, userdata->pw_dir);
2343
2344
2345
#else
	    tilde_dir = strdup("");
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2346
	}
2347

2348
2349
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2350

2351
2352
2353
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2354

2355
    return retval;
2356
2357
}

2358
#if defined(ENABLE_TABCOMP) || defined(ENABLE_BROWSER)
2359
/* Our sort routine for file listings.  Sort alphabetically and
2360
 * case-insensitively, and sort directories before filenames. */
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
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;

2374
2375
2376
    /* 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
2377
     * have to use multibyte strcasecmp() instead. */
2378
    return mbstrcasecmp(a, b);
2379
}
2380
2381
2382
2383
2384

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

2388
2389
    for (; len > 0; len--)
	free(array[len - 1]);
2390

2391
2392
    free(array);
}
2393
2394
#endif

2395
#ifdef ENABLE_TABCOMP
2396
/* Is the given path a directory? */
2397
bool is_dir(const char *buf)
2398
{
2399
    char *dirptr;
2400
    struct stat fileinfo;
2401
2402
2403
2404
    bool retval;

    dirptr = real_dir_from_tilde(buf);

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

2407
    free(dirptr);
2408

2409
    return retval;
2410
}
Chris Allegretta's avatar
Chris Allegretta committed
2411

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2412
2413
2414
/* 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
2415
 * file, with the copyright years updated:
Chris Allegretta's avatar
Chris Allegretta committed
2416
2417
2418
 *
 * Termios command line History and Editting, originally
 * intended for NetBSD sh (ash)
2419
 * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
Chris Allegretta's avatar
Chris Allegretta committed
2420
2421
2422
2423
2424
2425
2426
2427
 *      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.
2428
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
2429

2430
/* We consider the first buf_len characters of buf for ~username tab
2431
2432
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
2433
	size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2434
{
2435
    char **matches = NULL;
2436
2437
2438
#ifdef HAVE_PWD_H
    const struct passwd *userdata;
#endif
2439

2440
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2441

2442
    *num_matches = 0;
2443

2444
#ifdef HAVE_PWD_H
2445
    while ((userdata = getpwent()) != NULL) {
2446
	if (strncmp(userdata->pw_name, buf + 1, buf_len - 1) == 0) {
2447
2448
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2449

2450
2451
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2452
2453
2454
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2455
2456
#endif

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2457
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
2458
2459
					sizeof(char *));
	    matches[*num_matches] = charalloc(strlen(userdata->pw_name) + 2);
2460
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2461
	    ++(*num_matches);
2462
	}
2463
2464
    }
    endpwent();
2465
#endif
2466

2467
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2468
2469
}

2470
/* We consider the first buf_len characters of buf for filename tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2471
 * completion. */
2472
char **cwd_tab_completion(const char *buf, bool allow_files, size_t
2473
	*num_matches, size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2474
{
2475
2476
    char *dirname = mallocstrcpy(NULL, buf);
    char *slash, *filename;
2477
2478
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2479
    DIR *dir;
2480
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2481

2482
    *num_matches = 0;
2483
    dirname[buf_len] = '\0';
2484

2485
    /* If there's a / in the name, split out filename and directory parts. */
2486
2487
2488
    slash = strrchr(dirname, '/');
    if (slash != NULL) {
	char *wasdirname = dirname;
2489

2490
2491
	filename = mallocstrcpy(NULL, ++slash);
	/* Cut off the filename part after the slash. */
2492
	*slash = '\0';
2493
	dirname = real_dir_from_tilde(dirname);
2494
2495
2496
2497
2498
2499
	/* 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);
	}
2500
	free(wasdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2501
    } else {
2502
	filename = dirname;
2503
	dirname = mallocstrcpy(NULL, present_path);
Chris Allegretta's avatar
Chris Allegretta committed
2504
2505
    }

2506
    assert(dirname[strlen(dirname) - 1] == '/');
2507

Chris Allegretta's avatar
Chris Allegretta committed
2508
    dir = opendir(dirname);
2509

2510
    if (dir == NULL) {
2511
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2512
	beep();
2513
2514
2515
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2516
    }
2517
2518
2519

    filenamelen = strlen(filename);

2520
    while ((nextdir = readdir(dir)) != NULL) {
2521
2522
	bool skip_match = FALSE;

Chris Allegretta's avatar
Chris Allegretta committed
2523
#ifdef DEBUG
2524
	fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2525
#endif
2526
	/* See if this matches. */
2527
	if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
2528
2529
		(*filename == '.' || (strcmp(nextdir->d_name, ".") != 0 &&
		strcmp(nextdir->d_name, "..") != 0))) {
2530
2531
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2532

2533
	    char *tmp = charalloc(strlen(dirname) + strlen(nextdir->d_name) + 1);
2534
2535
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

2536
2537
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2538
2539
2540
2541
2542
2543
2544
	     * 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. */
2545
	    if (!allow_files && !is_dir(tmp))
2546
2547
2548
		skip_match = TRUE;

	    free(tmp);
2549

2550
	    if (skip_match)
2551
		continue;
2552

2553
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
2554
					sizeof(char *));
2555
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2556
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2557
2558
	}
    }
2559

2560
2561
    closedir(dir);
    free(dirname);
2562
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2563

2564
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2565
2566
}

2567
/* Do tab completion.  place refers to how much the statusbar cursor
2568
2569
 * position should be advanced.  refresh_func is the function we will
 * call to refresh the edit window. */
2570
char *input_tab(char *buf, bool allow_files, size_t *place,
2571
	bool *lastwastab, void (*refresh_func)(void), bool *listed)
Chris Allegretta's avatar
Chris Allegretta committed
2572
{
2573
    size_t num_matches = 0, buf_len;
2574
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2575

2576
    assert(buf != NULL && place != NULL && *place <= strlen(buf) &&
2577
2578
2579
		lastwastab != NULL && refresh_func != NULL && listed != NULL);

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

2581
2582
    /* 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
2583
    if (*place > 0 && *buf == '~') {
2584
	const char *slash = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2585

2586
	if (slash == NULL || slash >= buf + *place)
2587
	    matches = username_tab_completion(buf, &num_matches, *place);
2588
    }
2589

2590
2591
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
2592
	matches = cwd_tab_completion(buf, allow_files, &num_matches, *place);
2593

2594
2595
2596
    buf_len = strlen(buf);

    if (num_matches == 0 || *place != buf_len)
2597
2598
2599
	beep();
    else {
	size_t match, common_len = 0;
2600
	char *mzero, *glued;
2601
	const char *lastslash = revstrstr(buf, "/", buf + *place);
2602
	size_t lastslash_len = (lastslash == NULL) ? 0 : lastslash - buf + 1;
2603
	char char1[MAXCHARLEN], char2[MAXCHARLEN];
2604
	int len1, len2;
2605

2606
	/* Get the number of characters that all matches have in common. */
2607
	while (TRUE) {
2608
	    len1 = parse_mbchar(matches[0] + common_len, char1, NULL);
2609
2610

	    for (match = 1; match < num_matches; match++) {
2611
2612
2613
		len2 = parse_mbchar(matches[match] + common_len, char2, NULL);

		if (len1 != len2 || strncmp(char1, char2, len2) != 0)
2614
2615
		    break;
	    }
2616

2617
	    if (match < num_matches || matches[0][common_len] == '\0')
2618
		break;
2619

2620
	    common_len += len1;
2621
	}
2622

2623
	mzero = charalloc(lastslash_len + common_len + 1);
2624
2625
2626

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

2628
	common_len += lastslash_len;
2629
	mzero[common_len] = '\0';
2630

2631
2632
2633
2634
	/* 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);

2635
	assert(common_len >= *place);
2636

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

2640
2641
	    assert(common_len > *place);
	}
2642

2643
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2644
	    beep();
2645

2646
	/* If the matches have something in common, show that part. */
2647
	if (common_len != *place) {
2648
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
2649
	    charmove(buf + common_len, buf + *place, buf_len - *place + 1);
2650
	    strncpy(buf, mzero, common_len);
2651
	    *place = common_len;
2652
2653
2654
	}

	if (!*lastwastab)
2655
	    *lastwastab = TRUE;
2656
	else if (num_matches > 1) {
2657
	    int longest_name = 0, ncols, editline = 0;
2658

2659
	    /* Sort the list of available choices. */
2660
2661
	    qsort(matches, num_matches, sizeof(char *), diralphasort);

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

2666
2667
		if (namelen > longest_name)
		    longest_name = namelen;
2668
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2669

2670
2671
	    if (longest_name > COLS - 1)
		longest_name = COLS - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2672

2673
	    /* Each column will be (longest_name + 2) columns wide, i.e.
2674
2675
	     * two spaces between columns, except that there will be
	     * only one space after the last column. */
2676
	    ncols = (COLS + 1) / (longest_name + 2);
2677

2678
	    /* Blank the edit window and hide the cursor. */
2679
2680
	    blank_edit();
	    curs_set(0);
2681
	    wmove(edit, 0, 0);
2682

2683
	    /* Now print the list of matches out there. */
2684
2685
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2686

2687
		wmove(edit, editline, (longest_name + 2) * (match % ncols));
2688

2689
		if (match % ncols == 0 && editline == editwinrows - 1 &&
2690
			num_matches - match > ncols) {
2691
2692
2693
		    waddstr(edit, _("(more)"));
		    break;
		}
2694

2695
		disp = display_string(matches[match], 0, longest_name, FALSE);
2696
2697
		waddstr(edit, disp);
		free(disp);
2698

2699
		if ((match + 1) % ncols == 0)
2700
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2701
	    }
2702

2703
	    wnoutrefresh(edit);
2704
	    *listed = TRUE;
2705
2706
	}

2707
	free(glued);
2708
	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2709
2710
    }

2711
    free_chararray(matches, num_matches);
2712

2713
2714
2715
    /* 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)
2716
2717
	refresh_func();

2718
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2719
}
2720
#endif /* ENABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2721

2722
2723
/* Return the filename part of the given path. */
const char *tail(const char *path)
2724
{
2725
    const char *slash = strrchr(path, '/');
2726

2727
2728
    if (slash == NULL)
	return path;
2729
    else
2730
	return ++slash;
2731
2732
}

2733
2734
#ifndef DISABLE_HISTORIES
/* Return the constructed dirfile path, or NULL if we can't find the home
2735
 * directory.  The string is dynamically allocated, and should be freed. */
2736
char *construct_filename(const char *str)
2737
{
2738
    char *newstr = NULL;
2739

2740
2741
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2742

2743
2744
2745
	newstr = charalloc(homelen + strlen(str) + 1);
	strcpy(newstr, homedir);
	strcpy(newstr + homelen, str);
Chris Allegretta's avatar
Chris Allegretta committed
2746
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2747

2748
2749
2750
2751
2752
    return newstr;
}

char *histfilename(void)
{
2753
    return construct_filename("/.nano/search_history");
2754
2755
}

2756
2757
/* Construct the legacy history filename. */
/* (To be removed in 2018.) */
2758
2759
2760
2761
2762
2763
2764
char *legacyhistfilename(void)
{
    return construct_filename("/.nano_history");
}

char *poshistfilename(void)
{
2765
    return construct_filename("/.nano/filepos_history");
2766
2767
2768
2769
}

void history_error(const char *msg, ...)
{
2770
    va_list ap;
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780

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

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

2781
2782
2783
/* 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. */
2784
2785
int check_dotnano(void)
{
2786
    int ret = 1;
2787
2788
2789
2790
2791
    struct stat dirstat;
    char *nanodir = construct_filename("/.nano");

    if (stat(nanodir, &dirstat) == -1) {
	if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
2792
	    history_error(N_("Unable to create directory %s: %s\n"
2793
2794
				"It is required for saving/loading "
				"search history or cursor positions.\n"),
2795
				nanodir, strerror(errno));
2796
	    ret = 0;
2797
2798
	}
    } else if (!S_ISDIR(dirstat.st_mode)) {
2799
	history_error(N_("Path %s is not a directory and needs to be.\n"
2800
2801
				"Nano will be unable to load or save "
				"search history or cursor positions.\n"),
2802
				nanodir);
2803
	ret = 0;
2804
    }
2805
2806
2807

    free(nanodir);
    return ret;
2808
2809
}

2810
/* Load the search and replace histories from ~/.nano/search_history. */
2811
2812
2813
void load_history(void)
{
    char *nanohist = histfilename();
2814
2815
2816
    char *legacyhist = legacyhistfilename();
    struct stat hstat;

2817
2818
    /* If there is an old history file, migrate it. */
    /* (To be removed in 2018.) */
2819
    if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
2820
	if (rename(legacyhist, nanohist) == -1)
2821
2822
2823
	    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));
2824
	else
2825
2826
2827
	    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);
2828
2829
    }

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

2834
	if (hist == NULL) {
2835
	    if (errno != ENOENT) {
2836
2837
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2838
		history_error(N_("Error reading %s: %s"), nanohist,
2839
			strerror(errno));
2840
	    }
2841
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2842
2843
2844
	    /* 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. */
2845
	    filestruct **history = &search_history;
2846
	    char *line = NULL;
2847
	    size_t buf_len = 0;
2848
2849
	    ssize_t read;

2850
2851
	    while ((read = getline(&line, &buf_len, hist)) > 0) {
		line[--read] = '\0';
2852
2853
2854
		if (read > 0) {
		    /* Encode any embedded NUL as 0x0A. */
		    unsunder(line, read);
2855
		    update_history(history, line);
2856
		} else
2857
2858
		    history = &replace_history;
	    }
2859

2860
	    fclose(hist);
2861
	    free(line);
2862
	}
2863
	free(nanohist);
2864
	free(legacyhist);
2865
2866
2867
    }
}

2868
/* Write the lines of a history list, starting with the line at head, to
2869
2870
 * the open file at hist.  Return TRUE if the write succeeded, and FALSE
 * otherwise. */
2871
bool writehist(FILE *hist, const filestruct *head)
2872
{
2873
    const filestruct *item;
2874

2875
    /* Write a history list, from the oldest item to the newest. */
2876
2877
    for (item = head; item != NULL; item = item->next) {
	size_t length = strlen(item->data);
2878

2879
2880
2881
	/* Decode 0x0A bytes as embedded NULs. */
	sunder(item->data);

2882
2883
2884
	if (fwrite(item->data, sizeof(char), length, hist) < length)
	    return FALSE;
	if (putc('\n', hist) == EOF)
2885
	    return FALSE;
2886
    }
2887

2888
    return TRUE;
2889
2890
}

2891
/* Save the search and replace histories to ~/.nano/search_history. */
2892
2893
void save_history(void)
{
2894
    char *nanohist;
2895

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2896
    /* Don't save unchanged or empty histories. */
2897
    if (!history_has_changed() || (searchbot->lineno == 1 &&
2898
		replacebot->lineno == 1))
2899
2900
	return;

2901
2902
2903
2904
    nanohist = histfilename();

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

2906
	if (hist == NULL)
2907
2908
	    fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
			strerror(errno));
2909
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2910
2911
	    /* Make sure no one else can read from or write to the
	     * history file. */
2912
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2913

2914
	    if (!writehist(hist, searchage) || !writehist(hist, replaceage))
2915
		fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
2916
			strerror(errno));
2917

2918
2919
	    fclose(hist);
	}
2920

2921
2922
2923
	free(nanohist);
    }
}
2924

2925
/* Save the recorded last file positions to ~/.nano/filepos_history. */
2926
2927
void save_poshistory(void)
{
2928
    char *poshist = poshistfilename();
2929
    poshiststruct *posptr;
2930
    FILE *hist;
2931

2932
2933
    if (poshist == NULL)
	return;
2934

2935
    hist = fopen(poshist, "wb");
2936

2937
2938
2939
2940
2941
    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);
2942

2943
	for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2944
	    char *path_and_place;
2945
2946
	    size_t length;

2947
2948
	    /* Assume 20 decimal positions each for line and column number,
	     * plus two spaces, plus the line feed, plus the null byte. */
2949
2950
	    path_and_place = charalloc(strlen(posptr->filename) + 44);
	    sprintf(path_and_place, "%s %ld %ld\n", posptr->filename,
2951
			(long)posptr->lineno, (long)posptr->xno);
2952
	    length = strlen(path_and_place);
2953
2954

	    /* Encode newlines in filenames as nulls. */
2955
	    sunder(path_and_place);
2956
	    /* Restore the terminating newline. */
2957
	    path_and_place[length - 1] = '\n';
2958

2959
	    if (fwrite(path_and_place, sizeof(char), length, hist) < length)
2960
2961
		fprintf(stderr, _("Error writing %s: %s\n"),
					poshist, strerror(errno));
2962
	    free(path_and_place);
2963
	}
2964
	fclose(hist);
2965
    }
2966
    free(poshist);
2967
2968
}

2969
2970
/* 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. */
2971
2972
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
2973
    poshiststruct *posptr, *theone, *posprev = NULL;
2974
    char *fullpath = get_full_path(filename);
2975

2976
    if (fullpath == NULL || fullpath[strlen(fullpath) - 1] == '/' || inhelp) {
2977
	free(fullpath);
2978
	return;
2979
    }
2980

2981
    /* Look for a matching filename in the list. */
2982
    for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2983
2984
	if (!strcmp(posptr->filename, fullpath))
	    break;
2985
2986
2987
	posprev = posptr;
    }

2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
    /* 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;
    }

3002
    theone = posptr;
3003

3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
    /* 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) {
3014
3015
3016
3017
	if (posprev == NULL)
	    position_history = posptr->next;
	else
	    posprev->next = posptr->next;
3018
3019
3020
3021
3022
3023
3024
3025
3026
	while (posptr->next != NULL)
	    posptr = posptr->next;
	posptr->next = theone;
    }

    /* Store the last cursor position. */
    theone->lineno = lineno;
    theone->xno = xpos;
    theone->next = NULL;
3027
3028
3029
3030

    free(fullpath);
}

3031
3032
3033
3034
/* 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)
3035
{
3036
    poshiststruct *posptr = position_history;
3037
3038
3039
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
3040
3041
3042
3043
	return FALSE;

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

3045
    free(fullpath);
3046
3047

    if (posptr == NULL)
3048
	return FALSE;
3049
3050
3051
3052

    *line = posptr->lineno;
    *column = posptr->xno;
    return TRUE;
3053
3054
}

3055
/* Load the recorded file positions from ~/.nano/filepos_history. */
3056
3057
void load_poshistory(void)
{
3058
    char *poshist = poshistfilename();
3059
    FILE *hist;
3060

3061
3062
3063
    /* If the home directory is missing, do_rcfile() will have reported it. */
    if (poshist == NULL)
	return;
3064

3065
    hist = fopen(poshist, "rb");
3066

3067
3068
    if (hist == NULL) {
	if (errno != ENOENT) {
3069
	    /* When reading failed, don't save history when we quit. */
3070
3071
	    UNSET(POS_HISTORY);
	    history_error(N_("Error reading %s: %s"), poshist, strerror(errno));
3072
	}
3073
3074
3075
    } else {
	char *line = NULL, *lineptr, *xptr;
	size_t buf_len = 0;
3076
	ssize_t read, count = 0;
3077
3078
3079
	poshiststruct *record_ptr = NULL, *newrecord;

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

3084
3085
	    /* Find where the x index and line number are in the line. */
	    xptr = revstrstr(line, " ", line + read - 3);
3086
3087
	    if (xptr == NULL)
		continue;
3088
	    lineptr = revstrstr(line, " ", xptr - 2);
3089
3090
	    if (lineptr == NULL)
		continue;
3091
3092
3093
3094

	    /* Now separate the three elements of the line. */
	    *(xptr++) = '\0';
	    *(lineptr++) = '\0';
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109

	    /* 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;
3110
3111

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

3115
		position_history = position_history->next;
3116
3117
3118
3119

		free(drop_record->filename);
		free(drop_record);
	    }
3120
3121
3122
	}
	fclose(hist);
	free(line);
3123
    }
3124
    free(poshist);
3125
}
3126
#endif /* !DISABLE_HISTORIES */