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

22
#include "proto.h"
23

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

36
37
#define LOCKBUFSIZE 8192

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

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

60
    free(namecopy);
61

62
    return validity;
63
64
}

65
/* Add an entry to the circular list of openfile structs. */
66
void make_new_buffer(void)
67
{
68
69
    if (openfile == NULL) {
	openfile = make_new_opennode();
70

71
	/* Make the first open file the only element in the list. */
72
73
	openfile->prev = openfile;
	openfile->next = openfile;
74
    } else {
75
76
77
78
79
80
81
82
83
84
85
86
	openfilestruct *newnode = make_new_opennode();

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

	/* Make the new file the current one. */
	openfile = newnode;

	/* There is more than one file open: show Close in help lines. */
87
	exitfunc->desc = close_tag;
Chris Allegretta's avatar
Chris Allegretta committed
88
    }
89

90
    /* Start initializing the new buffer. */
91
    openfile->filename = mallocstrcpy(NULL, "");
Chris Allegretta's avatar
Chris Allegretta committed
92

93
    initialize_buffer_text();
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
94

95
    openfile->placewewant = 0;
96
    openfile->current_y = 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
97

98
    openfile->modified = FALSE;
99
#ifndef NANO_TINY
100
    openfile->mark_set = FALSE;
101
102
    openfile->mark_begin = NULL;
    openfile->mark_begin_x = 0;
103
    openfile->kind_of_mark = SOFTMARK;
104

105
    openfile->fmt = NIX_FILE;
106

107
108
    openfile->undotop = NULL;
    openfile->current_undo = NULL;
109
    openfile->last_action = OTHER;
110
111

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

120
/* Initialize the text of the current openfile struct. */
121
void initialize_buffer_text(void)
122
{
123
    assert(openfile != NULL);
124

125
126
    openfile->fileage = make_new_node(NULL);
    openfile->fileage->data = mallocstrcpy(NULL, "");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
127

128
129
130
131
    openfile->filebot = openfile->fileage;
    openfile->edittop = openfile->fileage;
    openfile->current = openfile->fileage;

132
    openfile->firstcolumn = 0;
133
    openfile->current_x = 0;
134
    openfile->totsize = 0;
135
136
}

137
138
139
140
141
142
143
144
145
146
147
/* 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
148
    if (!ISSET(LOCKING) || openfile->filename[0] == '\0')
149
150
	return;

151
    if (openfile->lock_filename != NULL) {
152
	char *fullname = get_full_path(openfile->filename);
153

154
155
156
157
158
159
	write_lockfile(openfile->lock_filename, fullname, TRUE);
	free(fullname);
    }
#endif
}

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

185
    mypid = getpid();
186
    myuid = geteuid();
187
188

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

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

206
    /* If the lockfile exists, try to delete it. */
207
208
    if (stat(lockfilename, &fileinfo) != -1)
	if (delete_lockfile(lockfilename) < 0)
209
	    goto free_the_data;
210
211

    if (ISSET(INSECURE_BACKUP))
212
	cflags = O_WRONLY | O_CREAT | O_APPEND;
213
    else
214
	cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;
215

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

225
    /* Try to associate a stream with the now open lockfile. */
226
227
    filestream = fdopen(fd, "wb");

228
    if (filestream == NULL) {
229
	statusline(MILD, _("Error writing lock file %s: %s"), lockfilename,
230
			strerror(errno));
231
	goto free_the_data;
232
233
    }

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

    wroteamt = fwrite(lockdata, sizeof(char), lockdatalen, filestream);
    if (wroteamt < lockdatalen) {
267
	statusline(MILD, _("Error writing lock file %s: %s"),
268
			lockfilename, ferror(filestream));
269
	goto free_the_data;
270
271
272
    }

#ifdef DEBUG
273
    fprintf(stderr, "In write_lockfile(), write successful (wrote %lu bytes)\n", (unsigned long)wroteamt);
274
#endif
275
276

    if (fclose(filestream) == EOF) {
277
	statusline(MILD, _("Error writing lock file %s: %s"),
278
			lockfilename, strerror(errno));
279
	goto free_the_data;
280
281
    }

282
    openfile->lock_filename = (char *) lockfilename;
283

Benno Schulenberg's avatar
Benno Schulenberg committed
284
    free(lockdata);
285
    return 1;
Benno Schulenberg's avatar
Benno Schulenberg committed
286

287
  free_the_data:
Benno Schulenberg's avatar
Benno Schulenberg committed
288
    free(lockdata);
289
    return 0;
290
291
292
#else
    return 1;
#endif
293
294
}

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

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

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

332
	if ((lockfd = open(lockfilename, O_RDONLY)) < 0) {
333
	    statusline(MILD, _("Error opening lock file %s: %s"),
334
			lockfilename, strerror(errno));
335
	    goto free_the_name;
336
	}
337

338
	lockbuf = charalloc(LOCKBUFSIZE);
339
	do {
340
	    readamt = read(lockfd, &lockbuf[readtot], LOCKBUFSIZE - readtot);
341
	    readtot += readamt;
342
	} while (readamt > 0 && readtot < LOCKBUFSIZE);
343

344
345
	close(lockfd);

346
	if (readtot < 48) {
347
	    statusline(MILD, _("Error reading lock file %s: "
348
			"Not enough data read"), lockfilename);
349
350
	    free(lockbuf);
	    goto free_the_name;
351
	}
352

353
	strncpy(lockprog, &lockbuf[2], 10);
354
355
	lockpid = (((unsigned char)lockbuf[27] * 256 + (unsigned char)lockbuf[26]) * 256 +
			(unsigned char)lockbuf[25]) * 256 + (unsigned char)lockbuf[24];
356
	strncpy(lockuser, &lockbuf[28], 16);
357
358
	free(lockbuf);

359
360
361
	pidstring = charalloc(11);
	sprintf (pidstring, "%u", (unsigned int)lockpid);

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

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

390
	response = do_yesno_prompt(FALSE, promptstr);
391
392
	free(promptstr);

393
	if (response < 1) {
394
	    blank_statusbar();
395
	    goto free_the_name;
396
	}
397
    }
398
399
400
401

    retval = write_lockfile(lockfilename, filename, FALSE);

  free_the_name:
402
    free(namecopy);
403
404
405
406
    if (retval < 1)
	free(lockfilename);

    return retval;
407
}
408
409
410
411
412
413
414
415
416
417
418
419
420
421

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

424
425
426
/* 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. */
427
bool open_buffer(const char *filename, bool undoable)
428
{
429
430
    bool new_buffer = (openfile == NULL || ISSET(MULTIBUFFER));
	/* Whether we load into the current buffer or a new one. */
431
432
    char *realname;
	/* The filename after tilde expansion. */
433
434
435
436
    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. */
437

438
439
    assert(filename != NULL);

440
441
442
    /* Display newlines in filenames as ^J. */
    as_an_at = FALSE;

443
444
#ifndef DISABLE_OPERATINGDIR
    if (check_operating_dir(filename, FALSE)) {
445
	statusline(ALERT, _("Can't insert file from outside of %s"),
446
				full_operating_dir);
447
	return FALSE;
448
449
    }
#endif
450

451
452
    realname = real_dir_from_tilde(filename);

453
454
    /* When the specified filename is not empty, and the corresponding
     * file exists, verify that it is a normal file. */
455
456
457
    if (strcmp(filename, "") != 0) {
	struct stat fileinfo;

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

468
    /* If we're going to load into a new buffer, first create the new
469
     * buffer and (if possible) lock the corresponding file. */
470
    if (new_buffer) {
471
	make_new_buffer();
472

473
	if (has_valid_path(realname)) {
474
#ifndef NANO_TINY
475
	    if (ISSET(LOCKING) && filename[0] != '\0') {
476
		/* When not overriding an existing lock, discard the buffer. */
477
		if (do_lockfile(realname) < 0) {
478
#ifndef DISABLE_MULTIBUFFER
479
		    close_buffer();
480
#endif
481
482
		    free(realname);
		    return FALSE;
483
		}
484
	    }
485
#endif /* !NANO_TINY */
486
	}
487
488
    }

489
490
491
    /* 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)) ?
492
		open_file(realname, new_buffer, FALSE, &f) : -2;
493

494
495
    /* If we have a file, and we're loading into a new buffer, update
     * the filename. */
496
    if (rc != -1 && new_buffer)
497
	openfile->filename = mallocstrcpy(openfile->filename, realname);
498

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

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

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

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

535
    assert(filename != NULL && filename[0] != '\0');
536

537
    /* Open the file quietly. */
538
539
540
541
542
    descriptor = open_file(filename, FALSE, TRUE, &f);

    /* If opening failed, forget it. */
    if (descriptor < 0)
	return;
543
544
545
546
547

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

548
549
    /* Insert the processed file into its place. */
    read_file(f, descriptor, filename, FALSE, TRUE);
550
551
552

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

#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);
}
#endif /* !ENABLE_TINY */
587
588
#endif /* !DISABLE_SPELLER */

589
/* Update the screen to account for the current buffer. */
590
void display_buffer(void)
591
{
592
    /* Update the titlebar, since the filename may have changed. */
593
    titlebar(NULL);
594

595
#ifndef DISABLE_COLOR
596
    /* Make sure we're using the buffer's associated colors. */
597
    color_init();
598
599
600
601
602
603

    /* 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();
604
605
#endif

606
607
    /* Update the content of the edit window straightaway. */
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
608
609
}

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

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

623
624
    /* Switch to the next or previous file buffer. */
    openfile = to_next ? openfile->next : openfile->prev;
625
626

#ifdef DEBUG
627
    fprintf(stderr, "filename is %s\n", openfile->filename);
628
629
#endif

630
631
632
633
634
635
636
637
#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

638
    /* Update the screen to account for the current buffer. */
639
    display_buffer();
640

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

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

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

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

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

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

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

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

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

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

690
    return TRUE;
691
}
692
#endif /* !DISABLE_MULTIBUFFER */
693

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

    if (ISSET(VIEW_MODE))
	return TRUE;

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

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

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

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

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

    return result;
731
732
}

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

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

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

779
    buf = charalloc(bufx);
780

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

    if (ISSET(SOFTWRAP))
	was_leftedge = (xplustabs() / editwincols) * editwincols;
787
788
#endif

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

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

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

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

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

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

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

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

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

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

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

903
    free(buf);
904

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

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

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

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

939
#ifndef NANO_TINY
940
941
942
    if (undoable)
	update_undo(INSERT);

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

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

958
    assert(filename != NULL && f != NULL);
959

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

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

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

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

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

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

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

1008
1009
    free(full_filename);

1010
1011
1012
    return fd;
}

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

1023
    assert(name != NULL && suffix != NULL);
1024

1025
    wholenamelen = strlen(name) + strlen(suffix);
1026

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

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

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

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

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

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

1048
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
1049
1050
}

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

1063
    /* Display newlines in filenames as ^J. */
1064
1065
    as_an_at = FALSE;

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

1087
1088
	present_path = mallocstrcpy(present_path, "./");

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

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

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

1133
#ifndef DISABLE_BROWSER
1134
	    if (func == to_files_void) {
1135
		char *chosen = do_browse_from(answer);
1136

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

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

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

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

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

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

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

1203
1204
		/* Update current_y to account for inserted lines. */
		place_the_cursor();
1205

1206
		refresh_needed = TRUE;
1207
	    }
1208

1209
1210
1211
	    break;
	}
    }
1212
1213

    free(given);
1214
1215
}

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

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

1244
    if (origpath == NULL)
1245
	return NULL;
1246

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

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

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

1273
    d_there = real_dir_from_tilde(origpath);
1274

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

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

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

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

1293
1294
1295
1296
    /* 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);
1297

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

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

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

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

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

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

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

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

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

1358
    return d_there;
1359
}
1360

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

1368
    if (full_path == NULL)
1369
	return NULL;
1370

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

    return full_path;
}

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

1392
1393
    assert(f != NULL);

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

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

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

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

1413
1414
1415
1416
1417
1418
1419
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

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

1425
1426
    umask(original_umask);

1427
    return full_tempdir;
1428
}
1429
1430

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

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

1439
1440
    full_operating_dir = get_full_path(operating_dir);

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

1445
    snuggly_fit(&full_operating_dir);
1446
1447
}

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

1459
    char *fullpath;
1460
    bool retval = FALSE;
1461
    const char *whereami1, *whereami2 = NULL;
1462

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

1467
    assert(full_operating_dir != NULL);
1468
1469

    fullpath = get_full_path(currpath);
1470

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

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

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

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

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

    return response;
1515
1516
}

1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
void init_backup_dir(void)
{
    char *full_backup_dir;

    if (backup_dir == NULL)
	return;

    full_backup_dir = get_full_path(backup_dir);

    /* If get_full_path() failed or the backup directory is
     * inaccessible, unset backup_dir. */
    if (full_backup_dir == NULL ||
1529
		full_backup_dir[strlen(full_backup_dir) - 1] != '/') {
1530
1531
1532
1533
1534
1535
	free(full_backup_dir);
	free(backup_dir);
	backup_dir = NULL;
    } else {
	free(backup_dir);
	backup_dir = full_backup_dir;
1536
	snuggly_fit(&backup_dir);
1537
1538
    }
}
1539
#endif /* !NANO_TINY */
1540

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

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

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

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

1571
1572
1573
    return retval;
}

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

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

1619
1620
    if (!tmp)
	titlebar(NULL);
1621

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1622
    realname = real_dir_from_tilde(name);
1623

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1811
	fclose(backup_file);
1812
1813
	free(backupname);
    }
1814

1815
    skip_backup:
1816
#endif /* !NANO_TINY */
1817

1818
1819
    if (f_open == NULL) {
	original_umask = umask(0);
1820

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

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

1834
1835
1836
1837
	if (f == NULL) {
	    f = fopen(realname, "rb");

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

1844
	tempname = safe_tempfile(&f);
1845

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1961
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1962

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

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

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

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

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

1998
1999
#ifndef DISABLE_COLOR
	    /* See if the applicable syntax has changed. */
2000
	    color_update();
2001
	    color_init();
2002

2003
2004
	    char *newname = openfile->syntax ? openfile->syntax->name : "";

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

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

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

2029
    retval = TRUE;
2030
2031
2032

  cleanup_and_exit:
    free(realname);
2033
    free(tempname);
2034

2035
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2036
2037
}

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

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

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

2064
    retval = write_file(name, f_open, tmp, method, TRUE);
2065

2066
2067
    /* If we added a magicline, remove it now. */
    if (added_magicline)
2068
2069
	remove_magicline();

2070
    /* Unpartition the filestruct so that it contains all the text again. */
2071
    unpartition_filestruct(&filepart);
2072

2073
    if (old_modified)
2074
2075
2076
2077
	set_modified();

    return retval;
}
2078

2079
#endif /* !NANO_TINY */
2080

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

2099
    /* Display newlines in filenames as ^J. */
2100
2101
    as_an_at = FALSE;

2102
2103
2104
2105
    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
2106
2107
    }

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

    while (TRUE) {
	const char *msg;
2116
#ifndef NANO_TINY
2117
2118
	const char *formatstr, *backupstr;

2119
	formatstr = (openfile->fmt == DOS_FILE) ? _(" [DOS Format]") :
2120
			(openfile->fmt == MAC_FILE) ? _(" [Mac Format]") : "";
2121

2122
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2123

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

2139
2140
	present_path = mallocstrcpy(present_path, "./");

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

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

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

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

2177
	    given = mallocstrcpy(given, answer);
Chris Allegretta's avatar
Chris Allegretta committed
2178

2179
#ifndef DISABLE_BROWSER
2180
	    if (func == to_files_void) {
2181
		char *chosen = do_browse_from(answer);
2182

2183
		if (chosen == NULL)
2184
		    continue;
2185

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

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

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

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

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

2250
2251
		free(full_filename);
		free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2252

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

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

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

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

2316
2317
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2318
    }
2319

2320
    free(given);
2321

2322
    return result ? 1 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
2323
2324
}

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

2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
#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

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

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

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

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

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

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

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

2384
2385
2386
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2387

2388
    return retval;
2389
2390
}

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

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

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

2421
2422
    for (; len > 0; len--)
	free(array[len - 1]);
2423

2424
2425
    free(array);
}
2426
2427
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2428
#ifndef DISABLE_TABCOMP
2429
/* Is the given path a directory? */
2430
bool is_dir(const char *buf)
2431
{
2432
    char *dirptr;
2433
    struct stat fileinfo;
2434
2435
2436
2437
    bool retval;

    dirptr = real_dir_from_tilde(buf);

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

2440
    free(dirptr);
2441

2442
    return retval;
2443
}
Chris Allegretta's avatar
Chris Allegretta committed
2444

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

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

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

2472
    *num_matches = 0;
2473

2474
2475
2476
#ifdef HAVE_PWD_H
    const struct passwd *userdata;

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

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

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

2499
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2500
2501
}

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

2514
    *num_matches = 0;
2515
    dirname[buf_len] = '\0';
2516

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

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

2538
    assert(dirname[strlen(dirname) - 1] == '/');
2539

Chris Allegretta's avatar
Chris Allegretta committed
2540
    dir = opendir(dirname);
2541

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

    filenamelen = strlen(filename);

2552
    while ((nextdir = readdir(dir)) != NULL) {
2553
2554
	bool skip_match = FALSE;

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

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

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

	    free(tmp);
2581

2582
	    if (skip_match)
2583
		continue;
2584

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

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

2596
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2597
2598
}

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

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

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

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

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

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

2626
2627
2628
    buf_len = strlen(buf);

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

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

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

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

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

2652
	    common_len += len1;
2653
	}
2654

2655
	mzero = charalloc(lastslash_len + common_len + 1);
2656
2657
2658

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

2660
	common_len += lastslash_len;
2661
	mzero[common_len] = '\0';
2662

2663
2664
2665
2666
	/* 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);

2667
	assert(common_len >= *place);
2668

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

2672
2673
	    assert(common_len > *place);
	}
2674

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

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

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

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

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

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

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

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

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

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

2719
		wmove(edit, editline, (longest_name + 2) * (match % ncols));
2720

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

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

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

2735
	    wnoutrefresh(edit);
2736
	    *listed = TRUE;
2737
2738
	}

2739
	free(glued);
2740
	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2741
2742
    }

2743
    free_chararray(matches, num_matches);
2744

2745
2746
2747
    /* 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)
2748
2749
	refresh_func();

2750
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2751
}
2752
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2753

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

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

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

2772
2773
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2774

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

2780
2781
2782
2783
2784
    return newstr;
}

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

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

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

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

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

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

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

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

    free(nanodir);
    return ret;
2840
2841
}

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

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

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

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

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

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

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

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

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

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

2920
    return TRUE;
2921
2922
}

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

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

2933
2934
2935
2936
    nanohist = histfilename();

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

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

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

2950
2951
	    fclose(hist);
	}
2952

2953
2954
2955
	free(nanohist);
    }
}
2956

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

2964
2965
    if (poshist == NULL)
	return;
2966

2967
    hist = fopen(poshist, "wb");
2968

2969
2970
2971
2972
2973
    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);
2974

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

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

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

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

3001
3002
/* 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. */
3003
3004
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
3005
    poshiststruct *posptr, *theone, *posprev = NULL;
3006
    char *fullpath = get_full_path(filename);
3007

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

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

3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
    /* 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;
    }

3034
    theone = posptr;
3035

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

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

    free(fullpath);
}

3063
3064
3065
3066
/* 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)
3067
{
3068
    poshiststruct *posptr = position_history;
3069
3070
3071
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
3072
3073
3074
3075
	return FALSE;

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

3077
    free(fullpath);
3078
3079
3080
3081
3082
3083
3084

    if (posptr == NULL)
        return FALSE;

    *line = posptr->lineno;
    *column = posptr->xno;
    return TRUE;
3085
3086
}

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

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

3097
    hist = fopen(poshist, "rb");
3098

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

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

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

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

	    /* 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;
3142
3143

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

3147
		position_history = position_history->next;
3148
3149
3150
3151

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