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

22
#include "proto.h"
23

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

33
34
#define LOCKBUFSIZE 8192

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

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

57
    free(namecopy);
58

59
    return validity;
60
61
}

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

67
    if (openfile == NULL) {
68
	/* Make the first open file the only element in the list. */
69
70
	newnode->prev = newnode;
	newnode->next = newnode;
71
    } else {
72
73
74
75
76
77
	/* Add the new open file after the current one in the list. */
	newnode->prev = openfile;
	newnode->next = openfile->next;
	openfile->next->prev = newnode;
	openfile->next = newnode;

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

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

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

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

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

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

99
    openfile->fmt = NIX_FILE;
100

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

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

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

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

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

129
130
131
132
133
134
135
136
137
138
139
/* Mark the current file as modified if it isn't already, and then
 * update the titlebar to display the file's new status. */
void set_modified(void)
{
    if (openfile->modified)
	return;

    openfile->modified = TRUE;
    titlebar(NULL);

#ifndef NANO_TINY
140
    if (!ISSET(LOCKING) || openfile->filename[0] == '\0')
141
142
	return;

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

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

152
#ifndef NANO_TINY
153
154
155
156
157
158
159
160
161
/* Actually write the lockfile.  This function will ALWAYS annihilate
 * any previous version of the file.  We'll borrow INSECURE_BACKUP here
 * to decide about lockfile paranoia here as well...
 *
 * Args:
 *     lockfilename: file name for lock
 *     origfilename: name of the file the lock is for
 *     modified: whether to set the modified bit in the file
 *
162
 * Returns 1 on success, and 0 on failure (but continue anyway). */
163
164
int write_lockfile(const char *lockfilename, const char *origfilename, bool modified)
{
165
#ifdef HAVE_PWD_H
166
167
168
169
170
    int cflags, fd;
    FILE *filestream;
    pid_t mypid;
    uid_t myuid;
    struct passwd *mypwuid;
171
    struct stat fileinfo;
172
173
    char *lockdata = charalloc(1024);
    char myhostname[32];
174
175
    size_t lockdatalen = 1024;
    size_t wroteamt;
176

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

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

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

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

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

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

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

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

226
    /* This is the lock data we will store:
227
228
229
230
     *
     * byte 0        - 0x62
     * byte 1        - 0x30
     * bytes 2-12    - program name which created the lock
231
     * bytes 24-27   - PID (little endian) of creator process
232
233
234
235
236
237
238
239
240
241
     * bytes 28-44   - username of who created the lock
     * bytes 68-100  - hostname of where the lock was created
     * bytes 108-876 - filename the lock is for
     * byte 1007     - 0x55 if file is modified
     *
     * Looks like VIM also stores undo state in this file, so we're
     * gonna have to figure out how to slap a 'OMG don't use recover on
     * our lockfile' message in here...
     *
     * This is likely very wrong, so this is a WIP. */
242
    memset(lockdata, 0, lockdatalen);
243
244
245
    lockdata[0] = 0x62;
    lockdata[1] = 0x30;
    lockdata[24] = mypid % 256;
246
247
248
    lockdata[25] = (mypid / 256) % 256;
    lockdata[26] = (mypid / (256 * 256)) % 256;
    lockdata[27] = mypid / (256 * 256 * 256);
249
    snprintf(&lockdata[2], 11, "nano %s", VERSION);
250
251
    strncpy(&lockdata[28], mypwuid->pw_name, 16);
    strncpy(&lockdata[68], myhostname, 31);
252
253
    if (origfilename != NULL)
	strncpy(&lockdata[108], origfilename, 768);
254
    if (modified == TRUE)
255
	lockdata[1007] = 0x55;
256
257
258

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

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

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

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

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

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

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

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

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

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

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

336
337
	close(lockfd);

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

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

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

354
#ifdef DEBUG
355
356
357
	fprintf(stderr, "lockpid = %d\n", lockpid);
	fprintf(stderr, "program name which created this lock file should be %s\n", lockprog);
	fprintf(stderr, "user which created this lock file should be %s\n", lockuser);
358
#endif
359
	/* TRANSLATORS: The second %s is the name of the user, the third that of the editor. */
360
361
362
	question = _("File %s is being edited (by %s with %s, PID %s); continue?");
	room = COLS - strlenpt(question) + 7 - strlenpt(lockuser) -
				strlenpt(lockprog) - strlenpt(pidstring);
363
	if (room < 4)
364
365
366
367
368
369
370
371
372
373
	    postedname = mallocstrcpy(NULL, "_");
	else if (room < strlenpt(filename)) {
	    char *fragment = display_string(filename,
				strlenpt(filename) - room + 3, room, FALSE);
	    postedname = charalloc(strlen(fragment) + 4);
	    strcpy(postedname, "...");
	    strcat(postedname, fragment);
	    free(fragment);
	} else
	    postedname = mallocstrcpy(NULL, filename);
374

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

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

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

    retval = write_lockfile(lockfilename, filename, FALSE);

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

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

/* Perform a stat call on the given filename, allocating a stat struct
 * if necessary.  On success, *pstat points to the stat's result.  On
 * failure, *pstat is freed and made NULL. */
void stat_with_alloc(const char *filename, struct stat **pstat)
{
    if (*pstat == NULL)
	*pstat = (struct stat *)nmalloc(sizeof(struct stat));

    if (stat(filename, *pstat) != 0) {
	free(*pstat);
	*pstat = NULL;
    }
}
414
#endif /* !NANO_TINY */
415

416
417
418
/* This does one of three things.  If the filename is "", just create a new
 * empty buffer.  Otherwise, read the given file into the existing buffer,
 * or into a new buffer when MULTIBUFFER is set or there is no buffer yet. */
419
bool open_buffer(const char *filename, bool undoable)
420
{
421
422
    bool new_buffer = (openfile == NULL || ISSET(MULTIBUFFER));
	/* Whether we load into the current buffer or a new one. */
423
424
    char *realname;
	/* The filename after tilde expansion. */
425
426
427
428
    FILE *f;
    int rc;
	/* rc == -2 means that we have a new file.  -1 means that the
	 * open() failed.  0 means that the open() succeeded. */
429

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

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

441
442
    realname = real_dir_from_tilde(filename);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    if (descriptor < 0)
	return;

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

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

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

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

584
#ifndef DISABLE_COLOR
585
586
587
588
589
    /* If there are multiline coloring regexes, and there is no
     * multiline cache data yet, precalculate it now. */
    if (openfile->syntax && openfile->syntax->nmultis > 0 &&
		openfile->fileage->multidata == NULL)
	precalc_multicolorinfo();
590

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

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

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

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

614
615
616
617
618
619
620
621
#ifndef NANO_TINY
    /* When not in softwrap mode, make sure firstcolumn is zero.  It might
     * be nonzero if we had softwrap mode on while in this buffer, and then
     * turned softwrap mode off while in a different buffer. */
    if (!ISSET(SOFTWRAP))
	openfile->firstcolumn = 0;
#endif

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

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

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

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

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

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

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

653
654
655
656
/* Delete an entry from the circular list of open files, and switch to the
 * one after it.  Return TRUE on success, and FALSE if there are no other
 * open buffers. */
bool close_buffer(void)
657
{
658
    /* If only one file buffer is open, get out. */
659
    if (openfile == openfile->next)
660
	return FALSE;
661

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

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

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

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

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

682
683
684
685
/* Do a quick permissions check by verifying whether the file is appendable.
 * Err on the side of permissiveness (reporting TRUE when it might be wrong)
 * to not fluster users editing on odd filesystems by printing incorrect
 * warnings. */
686
687
688
689
690
691
int is_file_writable(const char *filename)
{
    struct stat fileinfo, fileinfo2;
    int fd;
    FILE *f;
    char *full_filename;
692
    bool result = TRUE;
693
694
695
696
697
698
699

    if (ISSET(VIEW_MODE))
	return TRUE;

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

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

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

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

    return result;
717
718
}

719
720
721
/* Encode any NUL bytes in the given line of text, which is of length buf_len,
 * and return a dynamically allocated copy of the resultant string. */
char *encode_data(char *buf, size_t buf_len)
722
{
723
    unsunder(buf, buf_len);
724
    buf[buf_len] = '\0';
725

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

729
/* Read an open file into the current buffer.  f should be set to the
730
 * open file, and filename should be set to the name of the file.
731
732
733
 * undoable means do we want to create undo records to try and undo
 * this.  Will also attempt to check file writability if fd > 0 and
 * checkwritable == TRUE. */
734
735
void read_file(FILE *f, int fd, const char *filename, bool undoable,
		bool checkwritable)
736
{
737
738
    ssize_t was_lineno = openfile->current->lineno;
	/* The line number where we start the insertion. */
739
740
    size_t was_leftedge = 0;
	/* The leftedge where we start the insertion. */
741
742
743
744
745
746
747
    size_t num_lines = 0;
	/* The number of lines in the file. */
    size_t len = 0;
	/* The length of the current line of the file. */
    char input = '\0';
	/* The current input character. */
    char *buf;
748
749
750
	/* The buffer in which we assemble each line of the file. */
    size_t bufx = MAX_BUF_SIZE;
	/* The allocated size of the line buffer; increased as needed. */
751
752
753
754
    filestruct *topline;
	/* The top of the new buffer where we store the read file. */
    filestruct *bottomline;
	/* The bottom of the new buffer. */
755
756
757
    int input_int;
	/* The current value we read from the file, whether an input
	 * character or EOF. */
758
    bool writable = TRUE;
759
	/* Whether the file is writable (in case we care). */
760
#ifndef NANO_TINY
761
762
    int format = 0;
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
763
#endif
764

765
    buf = charalloc(bufx);
766

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

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

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

    /* Read the entire file into the new buffer. */
780
781
782
783
784
785
    while ((input_int = getc(f)) != EOF) {
	input = (char)input_int;

	/* If it's a *nix file ("\n") or a DOS file ("\r\n"), and file
	 * conversion isn't disabled, handle it! */
	if (input == '\n') {
786
#ifndef NANO_TINY
787
788
789
790
	    /* If it's a DOS file or a DOS/Mac file ('\r' before '\n' on
	     * the first line if we think it's a *nix file, or on any
	     * line otherwise), and file conversion isn't disabled,
	     * handle it! */
791
	    if ((num_lines == 0 || format != 0) && !ISSET(NO_CONVERT) &&
792
			len > 0 && buf[len - 1] == '\r') {
793
794
795
796
797
798
		if (format == 0 || format == 2)
		    format++;
	    }
	/* If it's a Mac file ('\r' without '\n' on the first line if we
	 * think it's a *nix file, or on any line otherwise), and file
	 * conversion isn't disabled, handle it! */
799
800
	} else if ((num_lines == 0 || format != 0) && !ISSET(NO_CONVERT) &&
			len > 0 && buf[len - 1] == '\r') {
801
802
803
804
805
	    /* If we currently think the file is a *nix file, set format
	     * to Mac.  If we currently think the file is a DOS file,
	     * set format to both DOS and Mac. */
	    if (format == 0 || format == 1)
		format += 2;
806
#endif
807
	} else {
808
809
810
	    /* Store the character. */
	    buf[len] = input;

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

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

#ifndef NANO_TINY
	/* If it's a DOS or Mac line, strip the '\r' from it. */
	if (len > 0 && buf[len - 1] == '\r' && !ISSET(NO_CONVERT))
	    buf[--len] = '\0';
#endif

	/* Store the data and make a new line. */
	bottomline->data = encode_data(buf, len);
	bottomline->next = make_new_node(bottomline);
	bottomline = bottomline->next;
	num_lines++;

	/* Reset the length in preparation for the next line. */
	len = 0;

#ifndef NANO_TINY
	/* If it happens to be a Mac line, store the character after the \r
	 * as the first character of the next line. */
	if (input != '\n')
	    buf[len++] = input;
#endif
845
846
847
848
849
850
    }

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

856
857
858
859
860
861
862
    /* If the file ended with newline, or it was entirely empty, make the
     * last line blank.  Otherwise, put the last read data in. */
    if (len == 0)
	bottomline->data = mallocstrcpy(NULL, "");
    else {
	bool mac_line_needs_newline = FALSE;

863
#ifndef NANO_TINY
864
865
866
867
868
869
	/* If the final character is '\r', and file conversion isn't disabled,
	 * set format to Mac if we currently think the file is a *nix file, or
	 * to DOS-and-Mac if we currently think it is a DOS file. */
	if (buf[len - 1] == '\r' && !ISSET(NO_CONVERT)) {
	    if (format < 2)
		format += 2;
870

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

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

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

889
    free(buf);
890

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

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

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

901
    if (!writable)
902
	statusline(ALERT, _("File '%s' is unwritable"), filename);
903
#ifndef NANO_TINY
904
    else if (format == 3) {
905
906
907
908
	/* TRANSLATORS: Keep the next four messages at most 78 characters. */
	statusline(HUSH, P_("Read %lu line (Converted from DOS and Mac format)",
			"Read %lu lines (Converted from DOS and Mac format)",
			(unsigned long)num_lines), (unsigned long)num_lines);
909
    } else if (format == 2) {
910
	openfile->fmt = MAC_FILE;
911
912
913
	statusline(HUSH, P_("Read %lu line (Converted from Mac format)",
			"Read %lu lines (Converted from Mac format)",
			(unsigned long)num_lines), (unsigned long)num_lines);
914
    } else if (format == 1) {
915
	openfile->fmt = DOS_FILE;
916
917
918
	statusline(HUSH, P_("Read %lu line (Converted from DOS format)",
			"Read %lu lines (Converted from DOS format)",
			(unsigned long)num_lines), (unsigned long)num_lines);
919
    }
920
#endif
921
    else
922
923
	statusline(HUSH, P_("Read %lu line", "Read %lu lines",
			(unsigned long)num_lines), (unsigned long)num_lines);
924

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

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

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

938
939
/* Open the file with the given name.  If the file does not exist, display
 * "New File" if newfie is TRUE, and say "File not found" otherwise.
940
 * Return -2 if we say "New File", -1 if the file isn't opened, and the
941
 * obtained fd otherwise.  *f is set to the opened file. */
942
int open_file(const char *filename, bool newfie, bool quiet, FILE **f)
943
{
944
    struct stat fileinfo, fileinfo2;
945
    int fd;
946
    char *full_filename;
947

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

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

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

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

970
971
972
    /* Don't open directories, character files, or block files. */
    if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
				S_ISBLK(fileinfo.st_mode)) {
973
	statusline(ALERT, S_ISDIR(fileinfo.st_mode) ?
974
975
			_("\"%s\" is a directory") :
			_("\"%s\" is a device file"), filename);
976
	free(full_filename);
977
	return -1;
978
979
980
981
982
983
984
985
986
    }

    /* Try opening the file. */
    fd = open(full_filename, O_RDONLY);

    if (fd == -1)
	statusline(ALERT, _("Error reading %s: %s"), filename, strerror(errno));
    else {
	/* The file is A-OK.  Associate a stream with it. */
987
	*f = fdopen(fd, "rb");
988

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

996
997
    free(full_filename);

998
999
1000
    return fd;
}

1001
1002
1003
1004
1005
/* This function will return the name of the first available extension
 * of a filename (starting with [name][suffix], then [name][suffix].1,
 * etc.).  Memory is allocated for the return value.  If no writable
 * extension exists, we return "". */
char *get_next_filename(const char *name, const char *suffix)
1006
{
1007
1008
    unsigned long i = 0;
    char *buf;
1009
    size_t wholenamelen;
1010

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1191
		refresh_needed = TRUE;
1192
	    }
1193

1194
1195
1196
	    break;
	}
    }
1197
1198

    free(given);
1199
1200
}

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

1214
/* When passed "[relative path]" or "[relative path][filename]" in
1215
 * origpath, return "[full path]" or "[full path][filename]" on success,
1216
1217
1218
1219
 * or NULL on error.  Do this if the file doesn't exist but the relative
 * path does, since the file could exist in memory but not yet on disk).
 * Don't do this if the relative path doesn't exist, since we won't be
 * able to go there. */
1220
char *get_full_path(const char *origpath)
1221
{
1222
    int attempts = 0;
1223
	/* How often we've tried climbing back up the tree. */
1224
    struct stat fileinfo;
1225
    char *currentdir, *d_here, *d_there, *d_there_file = NULL;
1226
    char *last_slash;
1227
    bool path_only;
1228

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

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

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

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

1258
    d_there = real_dir_from_tilde(origpath);
1259

1260
1261
1262
    /* If stat()ing d_there fails, assume that d_there refers to a new
     * file that hasn't been saved to disk yet.  Set path_only to TRUE
     * if d_there refers to a directory, and FALSE otherwise. */
1263
    path_only = (stat(d_there, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode));
1264

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

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

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

1278
1279
1280
1281
    /* If we didn't find one, then make sure the answer is in the format
     * "d_here/d_there". */
    if (last_slash == NULL) {
	assert(!path_only);
1282

1283
1284
1285
1286
1287
1288
1289
1290
	d_there_file = d_there;
	d_there = d_here;
    } else {
	/* If path_only is FALSE, then save the filename portion of the
	 * answer (everything after the last slash) in d_there_file. */
	if (!path_only)
	    d_there_file = mallocstrcpy(NULL, last_slash + 1);

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

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

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

	    /* If we succeeded, canonicalize it in d_there. */
	    if (d_there != NULL) {
		/* If the current directory isn't "/", tack a slash onto
		 * the end of it. */
		if (strcmp(d_there, "/") != 0) {
		    d_there = charealloc(d_there, strlen(d_there) + 2);
		    strcat(d_there, "/");
1312
		}
1313
1314
	    /* Otherwise, make sure that we return NULL. */
	    } else {
1315
		path_only = TRUE;
1316
1317
		free(currentdir);
	    }
1318
1319
1320

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

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

1329
1330
1331
1332
1333
1334
1335
    /* At this point, if path_only is FALSE and d_there isn't NULL,
     * d_there contains the path portion of the answer and d_there_file
     * contains the filename portion of the answer.  If this is the
     * case, tack the latter onto the end of the former.  d_there will
     * then contain the complete answer. */
    if (!path_only && d_there != NULL) {
	d_there = charealloc(d_there, strlen(d_there) +
1336
		strlen(d_there_file) + 1);
1337
1338
	strcat(d_there, d_there_file);
    }
1339

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

1343
    return d_there;
1344
}
1345

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

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

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

    return full_path;
}

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

1377
1378
1379
    /* If $TMPDIR is set, set tempdir to it, run it through
     * get_full_path(), and save the result in full_tempdir.  Otherwise,
     * leave full_tempdir set to NULL. */
1380
    tmpdir_env = getenv("TMPDIR");
1381
    if (tmpdir_env != NULL)
1382
	full_tempdir = check_writable_directory(tmpdir_env);
1383

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

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

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

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

    fd = mkstemp(full_tempdir);

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

1408
1409
    umask(original_umask);

1410
    return full_tempdir;
1411
}
1412
1413

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

1420
1421
    full_operating_dir = get_full_path(operating_dir);

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

1426
    snuggly_fit(&full_operating_dir);
1427
1428
}

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

1440
    char *fullpath;
1441
    bool retval = FALSE;
1442
    const char *whereami1, *whereami2 = NULL;
1443

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

1448
    fullpath = get_full_path(currpath);
1449

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

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

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

    /* Otherwise, we're still inside it. */
1474
    return retval;
1475
}
1476
1477
#endif

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

    return response;
1494
1495
}

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

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

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

1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
    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
1544

1545
1546
    if (fclose(inn) == EOF)
	retval = -1;
1547
    if (flush_out_fnc(out) == EOF)
1548
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1549

1550
1551
1552
    return retval;
}

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1595
    if (*name == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1596
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1597

1598
1599
    if (!tmp)
	titlebar(NULL);
1600

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1601
    realname = real_dir_from_tilde(name);
1602

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

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

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

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

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

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

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

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

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

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

1678
1679
1680
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1681
1682
1683
		}
	    }

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

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

1720
1721
	if (ISSET(INSECURE_BACKUP))
	    backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
1722
	else
1723
1724
1725
	    backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

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

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

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

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

#ifdef DEBUG
1769
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1770
1771
#endif

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

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

1790
	fclose(backup_file);
1791
1792
	free(backupname);
    }
1793

1794
    skip_backup:
1795
#endif /* !NANO_TINY */
1796

1797
1798
    if (f_open == NULL) {
	original_umask = umask(0);
1799

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

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

1813
1814
1815
1816
	if (f == NULL) {
	    f = fopen(realname, "rb");

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

1823
	tempname = safe_tempfile(&f);
1824

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

1831
	if (f_open == NULL) {
1832
	    fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1833

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

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

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

1862
1863
	/* Set the umask back to the user's original value. */
	umask(original_umask);
1864

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

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

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

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

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

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

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

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

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

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

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

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

1940
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1941

1942
1943
1944
1945
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1946
	}
1947

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

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

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

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

1978
1979
#ifndef DISABLE_COLOR
	    /* See if the applicable syntax has changed. */
1980
	    color_update();
1981
	    color_init();
1982

1983
	    newname = openfile->syntax ? openfile->syntax->name : "";
1984

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

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

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

2009
    retval = TRUE;
2010
2011
2012

  cleanup_and_exit:
    free(realname);
2013
    free(tempname);
2014

2015
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2016
2017
}

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

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

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

2044
    retval = write_file(name, f_open, tmp, method, TRUE);
2045

2046
2047
    /* If we added a magicline, remove it now. */
    if (added_magicline)
2048
2049
	remove_magicline();

2050
    /* Unpartition the buffer so that it contains all the text again. */
2051
    unpartition_filestruct(&filepart);
2052

2053
    if (old_modified)
2054
2055
2056
2057
	set_modified();

    return retval;
}
2058

2059
#endif /* !NANO_TINY */
2060

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

2079
    /* Display newlines in filenames as ^J. */
2080
2081
    as_an_at = FALSE;

2082
2083
2084
2085
    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
2086
2087
    }

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

    while (TRUE) {
	const char *msg;
2096
#ifndef NANO_TINY
2097
2098
	const char *formatstr, *backupstr;

2099
	formatstr = (openfile->fmt == DOS_FILE) ? _(" [DOS Format]") :
2100
			(openfile->fmt == MAC_FILE) ? _(" [Mac Format]") : "";
2101

2102
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2103

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

2120
2121
	present_path = mallocstrcpy(present_path, "./");

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

2137
	if (i < 0) {
2138
	    statusbar(_("Cancelled"));
2139
2140
	    break;
	} else {
2141
2142
	    functionptrtype func = func_from_key(&i);

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

2149
	    given = mallocstrcpy(given, answer);
Chris Allegretta's avatar
Chris Allegretta committed
2150

2151
#ifdef ENABLE_BROWSER
2152
	    if (func == to_files_void) {
2153
		char *chosen = do_browse_from(answer);
2154

2155
		if (chosen == NULL)
2156
		    continue;
2157

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

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

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

2206
	    if (method == OVERWRITE) {
2207
		bool name_exists, do_warning;
2208
		char *full_answer, *full_filename;
2209
2210
		struct stat st;

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

2222
2223
		free(full_filename);
		free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2224

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

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

2270
2271
		    warn_and_shortly_pause(_("File on disk has changed"));

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

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

2290
2291
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2292
    }
2293

2294
    free(given);
2295

2296
    return result ? 1 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
2297
2298
}

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

2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
#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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2324
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2325
	size_t i = 1;
2326
	char *tilde_dir;
2327

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

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

2340
2341
2342
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

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

2355
2356
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2357

2358
2359
2360
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2361

2362
    return retval;
2363
2364
}

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

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

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

2395
2396
    for (; len > 0; len--)
	free(array[len - 1]);
2397

2398
2399
    free(array);
}
2400
2401
#endif

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

    dirptr = real_dir_from_tilde(buf);

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

2414
    free(dirptr);
2415

2416
    return retval;
2417
}
Chris Allegretta's avatar
Chris Allegretta committed
2418

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

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

2447
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2448

2449
    *num_matches = 0;
2450

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

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

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

2474
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2475
2476
}

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

2489
    *num_matches = 0;
2490
    dirname[buf_len] = '\0';
2491

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

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

2513
    assert(dirname[strlen(dirname) - 1] == '/');
2514

Chris Allegretta's avatar
Chris Allegretta committed
2515
    dir = opendir(dirname);
2516

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

    filenamelen = strlen(filename);

2527
    while ((nextdir = readdir(dir)) != NULL) {
2528
2529
	bool skip_match = FALSE;

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

2540
	    char *tmp = charalloc(strlen(dirname) + strlen(nextdir->d_name) + 1);
2541
2542
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

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

	    free(tmp);
2556

2557
	    if (skip_match)
2558
		continue;
2559

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

2567
2568
    closedir(dir);
    free(dirname);
2569
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2570

2571
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2572
2573
}

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

2583
    assert(buf != NULL && place != NULL && *place <= strlen(buf) &&
2584
2585
2586
		lastwastab != NULL && refresh_func != NULL && listed != NULL);

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

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

2593
	if (slash == NULL || slash >= buf + *place)
2594
	    matches = username_tab_completion(buf, &num_matches, *place);
2595
    }
2596

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

2601
2602
2603
    buf_len = strlen(buf);

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

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

	    for (match = 1; match < num_matches; match++) {
2618
2619
2620
		len2 = parse_mbchar(matches[match] + common_len, char2, NULL);

		if (len1 != len2 || strncmp(char1, char2, len2) != 0)
2621
2622
		    break;
	    }
2623

2624
	    if (match < num_matches || matches[0][common_len] == '\0')
2625
		break;
2626

2627
	    common_len += len1;
2628
	}
2629

2630
	mzero = charalloc(lastslash_len + common_len + 1);
2631
2632
2633

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

2635
	common_len += lastslash_len;
2636
	mzero[common_len] = '\0';
2637

2638
2639
2640
2641
	/* 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);

2642
	assert(common_len >= *place);
2643

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

2647
2648
	    assert(common_len > *place);
	}
2649

2650
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2651
	    beep();
2652

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

	if (!*lastwastab)
2662
	    *lastwastab = TRUE;
2663
	else if (num_matches > 1) {
2664
	    int longest_name = 0, ncols, editline = 0;
2665

2666
	    /* Sort the list of available choices. */
2667
2668
	    qsort(matches, num_matches, sizeof(char *), diralphasort);

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

2673
2674
		if (namelen > longest_name)
		    longest_name = namelen;
2675
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2676

2677
2678
	    if (longest_name > COLS - 1)
		longest_name = COLS - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2679

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

2685
	    /* Blank the edit window and hide the cursor. */
2686
2687
	    blank_edit();
	    curs_set(0);
2688
	    wmove(edit, 0, 0);
2689

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

2694
		wmove(edit, editline, (longest_name + 2) * (match % ncols));
2695

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

2702
		disp = display_string(matches[match], 0, longest_name, FALSE);
2703
2704
		waddstr(edit, disp);
		free(disp);
2705

2706
		if ((match + 1) % ncols == 0)
2707
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2708
	    }
2709

2710
	    wnoutrefresh(edit);
2711
	    *listed = TRUE;
2712
2713
	}

2714
	free(glued);
2715
	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2716
2717
    }

2718
    free_chararray(matches, num_matches);
2719

2720
2721
2722
    /* 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)
2723
2724
	refresh_func();

2725
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2726
}
2727
#endif /* ENABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2728

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

2734
2735
    if (slash == NULL)
	return path;
2736
    else
2737
	return ++slash;
2738
2739
}

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

2747
2748
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2749

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

2755
2756
2757
2758
2759
    return newstr;
}

char *histfilename(void)
{
2760
    return construct_filename("/.nano/search_history");
2761
2762
}

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

char *poshistfilename(void)
{
2772
    return construct_filename("/.nano/filepos_history");
2773
2774
2775
2776
}

void history_error(const char *msg, ...)
{
2777
    va_list ap;
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787

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

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

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

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

    free(nanodir);
    return ret;
2815
2816
}

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

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

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

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

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

2867
	    fclose(hist);
2868
	    free(line);
2869
	}
2870
	free(nanohist);
2871
	free(legacyhist);
2872
2873
2874
    }
}

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

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

2886
2887
2888
	/* Decode 0x0A bytes as embedded NULs. */
	sunder(item->data);

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

2895
    return TRUE;
2896
2897
}

2898
/* Save the search and replace histories to ~/.nano/search_history. */
2899
2900
void save_history(void)
{
2901
    char *nanohist;
2902

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

2908
2909
2910
2911
    nanohist = histfilename();

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

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

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

2925
2926
	    fclose(hist);
	}
2927

2928
2929
2930
	free(nanohist);
    }
}
2931

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

2939
2940
    if (poshist == NULL)
	return;
2941

2942
    hist = fopen(poshist, "wb");
2943

2944
2945
2946
2947
2948
    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);
2949

2950
	for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2951
	    char *path_and_place;
2952
2953
	    size_t length;

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

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

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

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

2983
    if (fullpath == NULL || fullpath[strlen(fullpath) - 1] == '/' || inhelp) {
2984
	free(fullpath);
2985
	return;
2986
    }
2987

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

2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
    /* 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;
    }

3009
    theone = posptr;
3010

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

    /* Store the last cursor position. */
    theone->lineno = lineno;
    theone->xno = xpos;
    theone->next = NULL;
3034
3035
3036
3037

    free(fullpath);
}

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

    if (fullpath == NULL)
3047
3048
3049
3050
	return FALSE;

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

3052
    free(fullpath);
3053
3054

    if (posptr == NULL)
3055
	return FALSE;
3056
3057
3058
3059

    *line = posptr->lineno;
    *column = posptr->xno;
    return TRUE;
3060
3061
}

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

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

3072
    hist = fopen(poshist, "rb");
3073

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

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

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

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

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

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

3122
		position_history = position_history->next;
3123
3124
3125
3126

		free(drop_record->filename);
		free(drop_record);
	    }
3127
3128
3129
	}
	fclose(hist);
	free(line);
3130
    }
3131
    free(poshist);
3132
}
3133
#endif /* !DISABLE_HISTORIES */