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

22
#include "proto.h"
23

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

33
34
#define LOCKBUFSIZE 8192

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

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

57
    free(namecopy);
58

59
    return validity;
60
61
}

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

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

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

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

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

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

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

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

99
    openfile->fmt = NIX_FILE;
100

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

336
337
	close(lockfd);

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

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

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

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

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

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

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

    retval = write_lockfile(lockfilename, filename, FALSE);

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

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

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

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

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

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

433
#ifndef DISABLE_OPERATINGDIR
434
435
    if (outside_of_confinement(filename, FALSE)) {
	statusline(ALERT, _("Can't read file from outside of %s"),
436
				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
		refresh_needed = TRUE;
1189
	    }
1190

1191
1192
1193
	    break;
	}
    }
1194
1195

    free(given);
1196
1197
}

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

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

1226
    if (origpath == NULL)
1227
	return NULL;
1228

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

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

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

1255
    d_there = real_dir_from_tilde(origpath);
1256

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

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

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

1272
1273
    /* Search for the last slash in d_there. */
    last_slash = strrchr(d_there, '/');
1274

1275
1276
1277
1278
    /* 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);
1279

1280
1281
1282
1283
1284
1285
1286
1287
	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);

1288
	/* Remove the filename portion of the answer from d_there. */
1289
	*(last_slash + 1) = '\0';
1290

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

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

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

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

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

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

1337
    /* Free d_there_file, since we're done using it. */
1338
    free(d_there_file);
1339

1340
    return d_there;
1341
}
1342

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

1350
    if (full_path == NULL)
1351
	return NULL;
1352

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

    return full_path;
}

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

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

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

1386
1387
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
1388
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1389

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1390
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1391
    strcat(full_tempdir, "nano.XXXXXX");
1392

1393
1394
1395
1396
1397
1398
1399
    original_umask = umask(0);
    umask(S_IRWXG | S_IRWXO);

    fd = mkstemp(full_tempdir);

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

1405
1406
    umask(original_umask);

1407
    return full_tempdir;
1408
}
1409
1410

#ifndef DISABLE_OPERATINGDIR
1411
/* Change to the specified operating directory, when it's valid. */
1412
1413
void init_operating_dir(void)
{
1414
    operating_dir = free_and_assign(operating_dir, get_full_path(operating_dir));
1415

1416
    /* If the operating directory is inaccessible, fail. */
1417
    if (operating_dir == NULL || chdir(operating_dir) == -1)
1418
	die(_("Invalid operating directory\n"));
1419

1420
    snuggly_fit(&operating_dir);
1421
1422
}

1423
1424
1425
1426
/* Check whether the given path is outside of the operating directory.
 * Return TRUE if it is, and FALSE otherwise.  If allow_tabcomp is TRUE,
 * incomplete names that can grow into matches for the operating directory
 * are considered to be inside, so that tab completion will work. */
1427
bool outside_of_confinement(const char *currpath, bool allow_tabcomp)
1428
{
1429
    char *fullpath;
1430
    bool is_inside, begins_to_be;
1431

1432
    /* If no operating directory is set, there is nothing to check. */
1433
    if (operating_dir == NULL)
1434
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1435

1436
    fullpath = get_full_path(currpath);
1437

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1438
1439
1440
    /* 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
1441
     * non-existent directory as being outside the operating directory,
1442
     * so we return FALSE.  If allow_tabcomp is TRUE, then currpath
1443
     * exists, but is not executable.  So we say it is outside the
1444
     * operating directory. */
1445
    if (fullpath == NULL)
1446
	return allow_tabcomp;
1447

1448
1449
1450
    is_inside = (strstr(fullpath, operating_dir) == fullpath);
    begins_to_be = (allow_tabcomp &&
			strstr(operating_dir, fullpath) == operating_dir);
1451
    free(fullpath);
1452

1453
    return (!is_inside && !begins_to_be);
1454
}
1455
1456
#endif

1457
#ifndef NANO_TINY
1458
1459
/* Although this sucks, it sucks less than having a single 'my system is
 * messed up and I'm blanket allowing insecure file writing operations'. */
1460
1461
int prompt_failed_backupwrite(const char *filename)
{
1462
    static int response;
1463
    static char *prevfile = NULL; /* What was the last file we were
1464
1465
				   * passed so we don't keep asking
				   * this?  Though maybe we should... */
1466
    if (prevfile == NULL || strcmp(filename, prevfile)) {
1467
1468
	response = do_yesno_prompt(FALSE, _("Failed to write backup file; "
			"continue saving? (Say N if unsure.) "));
1469
1470
	prevfile = mallocstrcpy(prevfile, filename);
    }
1471
1472

    return response;
1473
1474
}

1475
1476
/* Transform the specified backup directory to an absolute path,
 * and verify that it is usable. */
1477
1478
void init_backup_dir(void)
{
1479
1480
1481
1482
1483
1484
1485
1486
    backup_dir = free_and_assign(backup_dir, get_full_path(backup_dir));

    /* If we can't get an absolute path (which means it doesn't exist or
       isn't accessible), or it's not a directory, fail. */
    if (backup_dir == NULL || backup_dir[strlen(backup_dir) - 1] != '/')
	die(_("Invalid backup directory\n"));

    snuggly_fit(&backup_dir);
1487
}
1488
#endif /* !NANO_TINY */
1489

1490
/* Read from inn, write to out.  We assume inn is opened for reading,
1491
 * and out for writing.  We return 0 on success, -1 on read error, or -2
1492
1493
1494
 * 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)
1495
{
1496
    int retval = 0;
1497
    char buf[BUFSIZ];
1498
    size_t charsread;
1499
    int (*flush_out_fnc)(FILE *) = (close_out) ? fclose : fflush;
1500

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

1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
    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
1514

1515
1516
    if (fclose(inn) == EOF)
	retval = -1;
1517
    if (flush_out_fnc(out) == EOF)
1518
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1519

1520
1521
1522
    return retval;
}

1523
1524
/* 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
1525
 * ourselves.  If tmp is TRUE, we set the umask to disallow anyone else
1526
1527
 * 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
1528
 *
1529
 * tmp means we are writing a temporary file in a secure fashion.  We
1530
 * use it when spell checking or dumping the file on an error.  If
1531
1532
 * method is APPEND, it means we are appending instead of overwriting.
 * If method is PREPEND, it means we are prepending instead of
1533
 * overwriting.  If nonamechange is TRUE, we don't change the current
1534
1535
 * filename.  nonamechange is irrelevant when appending or prepending,
 * or when writing a temporary file.
1536
 *
1537
 * Return TRUE on success or FALSE on error. */
1538
1539
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
1540
{
1541
    bool retval = FALSE;
1542
	/* Instead of returning in this function, you should always
1543
	 * set retval and then goto cleanup_and_exit. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1544
    size_t lineswritten = 0;
1545
    const filestruct *fileptr = openfile->fileage;
1546
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1547
	/* The file descriptor we use. */
1548
    mode_t original_umask = 0;
1549
	/* Our umask, from when nano started. */
1550
#ifndef NANO_TINY
1551
    bool realexists;
1552
	/* The result of stat().  TRUE if the file exists, FALSE
1553
	 * otherwise.  If name is a link that points nowhere, realexists
1554
	 * is FALSE. */
1555
#endif
1556
1557
1558
    struct stat st;
	/* The status fields filled in by stat(). */
    char *realname;
1559
	/* The filename after tilde expansion. */
1560
    FILE *f = f_open;
1561
1562
	/* The actual file, realname, we are writing to. */
    char *tempname = NULL;
1563
	/* The name of the temporary file we write to on prepend. */
Chris Allegretta's avatar
Chris Allegretta committed
1564

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1565
    if (*name == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1566
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1567

1568
1569
    if (!tmp)
	titlebar(NULL);
1570

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1571
    realname = real_dir_from_tilde(name);
1572

1573
#ifndef DISABLE_OPERATINGDIR
1574
    /* If we're writing a temporary file, we're probably going outside
1575
     * the operating directory, so skip the operating directory test. */
1576
    if (!tmp && outside_of_confinement(realname, FALSE)) {
1577
	statusline(ALERT, _("Can't write outside of %s"), operating_dir);
1578
	goto cleanup_and_exit;
1579
1580
1581
    }
#endif

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

1586
#ifndef NANO_TINY
1587
    /* Check whether the file (at the end of the symlink) exists. */
1588
    realexists = (stat(realname, &st) != -1);
1589

1590
1591
1592
1593
    /* 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. */
1594
1595
    if (openfile->current_stat == NULL && !tmp && realexists)
	stat_with_alloc(realname, &openfile->current_stat);
1596

1597
    /* We backup only if the backup toggle is set, the file isn't
1598
1599
1600
1601
     * 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. */
1602
    if (ISSET(BACKUP_FILE) && !tmp && realexists && openfile->current_stat &&
1603
		(method != OVERWRITE || openfile->mark_set ||
1604
		openfile->current_stat->st_mtime == st.st_mtime)) {
1605
	int backup_fd;
1606
	FILE *backup_file;
1607
	char *backupname;
1608
	static struct timespec filetime[2];
1609
	int backup_cflags;
1610

1611
	/* Save the original file's access and modification times. */
1612
1613
	filetime[0].tv_sec = openfile->current_stat->st_atime;
	filetime[1].tv_sec = openfile->current_stat->st_mtime;
1614

1615
1616
1617
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1618

1619
	    if (f == NULL) {
1620
		statusline(ALERT, _("Error reading %s: %s"), realname,
1621
			strerror(errno));
1622
1623
1624
1625
		/* 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;
1626
	    }
1627
1628
	}

1629
	/* If backup_dir is set, we set backupname to
1630
1631
1632
1633
	 * 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]. */
1634
	if (backup_dir != NULL) {
1635
	    char *backuptemp = get_full_path(realname);
1636

1637
	    if (backuptemp == NULL)
1638
1639
1640
1641
1642
1643
		/* 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~. */
1644
		backuptemp = mallocstrcpy(NULL, tail(realname));
1645
	    else {
1646
1647
		size_t i = 0;

1648
1649
1650
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1651
1652
1653
		}
	    }

1654
	    backupname = charalloc(strlen(backup_dir) + strlen(backuptemp) + 1);
1655
1656
1657
	    sprintf(backupname, "%s%s", backup_dir, backuptemp);
	    free(backuptemp);
	    backuptemp = get_next_filename(backupname, "~");
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1658
	    if (*backuptemp == '\0') {
1659
1660
		statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, _("Too many backup files?"));
1661
1662
		free(backuptemp);
		free(backupname);
1663
1664
1665
1666
1667
		/* 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! */
1668
		goto cleanup_and_exit;
1669
1670
1671
1672
	    } else {
		free(backupname);
		backupname = backuptemp;
	    }
1673
1674
1675
1676
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1677

1678
	/* First, unlink any existing backups.  Next, open the backup
1679
1680
	 * file with O_CREAT and O_EXCL.  If it succeeds, we have a file
	 * descriptor to a new backup file. */
1681
	if (unlink(backupname) < 0 && errno != ENOENT && !ISSET(INSECURE_BACKUP)) {
1682
1683
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1684
1685
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1686
1687
1688
1689
	    free(backupname);
	    goto cleanup_and_exit;
	}

1690
1691
	if (ISSET(INSECURE_BACKUP))
	    backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
1692
	else
1693
1694
1695
	    backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

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

1701
	if (backup_fd < 0 || backup_file == NULL) {
1702
1703
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1704
	    free(backupname);
1705
1706
1707
1708
	    /* 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! */
1709
1710
	    goto cleanup_and_exit;
	}
1711

1712
1713
1714
1715
1716
1717
	/* 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);
1718
1719
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1720
1721
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1722
1723
1724
1725
	    free(backupname);
	    goto cleanup_and_exit;
	}

1726
1727
1728
1729
	/* Set the backup's mode bits. */
	if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1 &&
			!ISSET(INSECURE_BACKUP)) {
	    fclose(backup_file);
1730
1731
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1732
1733
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1734
	    free(backupname);
1735
	    goto cleanup_and_exit;
1736
1737
1738
	}

#ifdef DEBUG
1739
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1740
1741
#endif

1742
	/* Copy the file. */
1743
1744
	if (copy_file(f, backup_file, FALSE) != 0) {
	    fclose(backup_file);
1745
	    statusline(ALERT, _("Error reading %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1746
			strerror(errno));
1747
1748
1749
	    goto cleanup_and_exit;
	}

1750
	/* And set the backup's timestamps. */
1751
1752
	if (futimens(backup_fd, filetime) == -1 && !ISSET(INSECURE_BACKUP)) {
	    fclose(backup_file);
1753
1754
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1755
1756
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1757
	    goto cleanup_and_exit;
1758
	}
1759

1760
	fclose(backup_file);
1761
1762
	free(backupname);
    }
1763

1764
    skip_backup:
1765
#endif /* !NANO_TINY */
1766

1767
1768
    if (f_open == NULL) {
	original_umask = umask(0);
1769

1770
	/* If we create a temp file, we don't let anyone else access it.
1771
1772
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1773
	    umask(S_IRWXG | S_IRWXO);
1774
1775
	else
	    umask(original_umask);
1776
    }
1777

1778
    /* If we're prepending, copy the file to a temp file. */
1779
    if (method == PREPEND) {
1780
1781
1782
	int fd_source;
	FILE *f_source = NULL;

1783
1784
1785
1786
	if (f == NULL) {
	    f = fopen(realname, "rb");

	    if (f == NULL) {
1787
		statusline(ALERT, _("Error reading %s: %s"), realname,
1788
1789
1790
1791
1792
			strerror(errno));
		goto cleanup_and_exit;
	    }
	}

1793
	tempname = safe_tempfile(&f);
1794

1795
	if (tempname == NULL) {
1796
	    statusline(ALERT, _("Error writing temp file: %s"),
1797
			strerror(errno));
1798
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1799
	}
1800

1801
	if (f_open == NULL) {
1802
	    fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1803

1804
1805
1806
	    if (fd_source != -1) {
		f_source = fdopen(fd_source, "rb");
		if (f_source == NULL) {
1807
		    statusline(ALERT, _("Error reading %s: %s"), realname,
1808
				strerror(errno));
1809
1810
1811
1812
1813
1814
		    close(fd_source);
		    fclose(f);
		    unlink(tempname);
		    goto cleanup_and_exit;
		}
	    }
1815
1816
	}

1817
	if (f_source == NULL || copy_file(f_source, f, TRUE) != 0) {
1818
	    statusline(ALERT, _("Error writing temp file: %s"),
1819
			strerror(errno));
1820
	    unlink(tempname);
1821
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1822
1823
1824
	}
    }

1825
1826
1827
    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*. */
1828
	fd = open(realname, O_WRONLY | O_CREAT | ((method == APPEND) ?
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1829
1830
		O_APPEND : (tmp ? O_EXCL : O_TRUNC)), S_IRUSR |
		S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1831

1832
1833
	/* Set the umask back to the user's original value. */
	umask(original_umask);
1834

1835
1836
	/* If we couldn't open the file, give up. */
	if (fd == -1) {
1837
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1838
			strerror(errno));
1839
1840
1841
1842
	    if (tempname != NULL)
		unlink(tempname);
	    goto cleanup_and_exit;
	}
1843

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

1846
	if (f == NULL) {
1847
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1848
			strerror(errno));
1849
1850
1851
	    close(fd);
	    goto cleanup_and_exit;
	}
1852
1853
    }

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

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

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

1862
1863
	/* Convert nulls to newlines.  data_len is the string's real
	 * length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1864
1865
	unsunder(fileptr->data, data_len);

1866
	if (size < data_len) {
1867
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1868
			strerror(errno));
1869
	    fclose(f);
1870
	    goto cleanup_and_exit;
1871
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1872

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1873
	/* If we're on the last line of the file, don't write a newline
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1874
1875
1876
1877
1878
1879
1880
	 * 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 {
1881
#ifndef NANO_TINY
1882
	    if (openfile->fmt == DOS_FILE || openfile->fmt == MAC_FILE) {
1883
		if (putc('\r', f) == EOF) {
1884
		    statusline(ALERT, _("Error writing %s: %s"), realname,
1885
				strerror(errno));
1886
1887
1888
		    fclose(f);
		    goto cleanup_and_exit;
		}
1889
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1890

1891
	    if (openfile->fmt != MAC_FILE)
1892
#endif
1893
		if (putc('\n', f) == EOF) {
1894
		    statusline(ALERT, _("Error writing %s: %s"), realname,
1895
				strerror(errno));
1896
1897
1898
1899
		    fclose(f);
		    goto cleanup_and_exit;
		}
	}
Chris Allegretta's avatar
Chris Allegretta committed
1900
1901
1902
1903
1904

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

1905
    /* If we're prepending, open the temp file, and append it to f. */
1906
    if (method == PREPEND) {
1907
1908
1909
	int fd_source;
	FILE *f_source = NULL;

1910
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1911

1912
1913
1914
1915
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1916
	}
1917

1918
	if (f_source == NULL) {
1919
1920
	    statusline(ALERT, _("Error reading %s: %s"), tempname,
			strerror(errno));
1921
	    fclose(f);
1922
	    goto cleanup_and_exit;
1923
1924
	}

1925
	if (copy_file(f_source, f, TRUE) != 0) {
1926
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1927
			strerror(errno));
1928
	    goto cleanup_and_exit;
1929
	}
1930
1931

	unlink(tempname);
1932
    } else if (fclose(f) != 0) {
1933
	statusline(ALERT, _("Error writing %s: %s"), realname,
1934
			strerror(errno));
1935
	goto cleanup_and_exit;
1936
    }
1937

1938
    if (method == OVERWRITE && !tmp) {
1939
1940
	/* If we must set the filename, and it changed, adjust things. */
	if (!nonamechange && strcmp(openfile->filename, realname) != 0) {
1941
#ifndef DISABLE_COLOR
1942
	    char *newname;
1943
	    char *oldname = openfile->syntax ? openfile->syntax->name : "";
1944
1945
1946
	    filestruct *line = openfile->fileage;
#endif
	    openfile->filename = mallocstrcpy(openfile->filename, realname);
1947

1948
1949
#ifndef DISABLE_COLOR
	    /* See if the applicable syntax has changed. */
1950
	    color_update();
1951
	    color_init();
1952

1953
	    newname = openfile->syntax ? openfile->syntax->name : "";
1954

1955
	    /* If the syntax changed, discard and recompute the multidata. */
1956
	    if (strcmp(oldname, newname) != 0) {
1957
1958
1959
1960
1961
		for (; line != NULL; line = line->next) {
		    free(line->multidata);
		    line->multidata = NULL;
		}
		precalc_multicolorinfo();
1962
		refresh_needed = TRUE;
1963
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1964
1965
#endif
	}
1966

1967
#ifndef NANO_TINY
1968
	if (!openfile->mark_set)
1969
1970
	    /* Get or update the stat info to reflect the current state. */
	    stat_with_alloc(realname, &openfile->current_stat);
1971
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1972

1973
1974
	statusline(HUSH, P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten), (unsigned long)lineswritten);
1975
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1976
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1977
    }
1978

1979
    retval = TRUE;
1980
1981
1982

  cleanup_and_exit:
    free(realname);
1983
    free(tempname);
1984

1985
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1986
1987
}

1988
#ifndef NANO_TINY
1989
1990
/* Write a marked selection from a file out to disk.  Return TRUE on
 * success or FALSE on error. */
1991
bool write_marked_file(const char *name, FILE *f_open, bool tmp,
1992
	kind_of_writing_type method)
1993
{
1994
    bool retval;
1995
    bool old_modified = openfile->modified;
1996
	/* Save the status, because write_file() unsets the modified flag. */
1997
    bool added_magicline = FALSE;
1998
1999
2000
2001
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

2002
    /* Partition the buffer so that it contains only the marked text. */
2003
    mark_order((const filestruct **)&top, &top_x,
2004
		(const filestruct **)&bot, &bot_x, NULL);
2005
    filepart = partition_filestruct(top, top_x, bot, bot_x);
2006

2007
2008
2009
    /* 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') {
2010
	new_magicline();
2011
2012
	added_magicline = TRUE;
    }
2013

2014
    retval = write_file(name, f_open, tmp, method, TRUE);
2015

2016
2017
    /* If we added a magicline, remove it now. */
    if (added_magicline)
2018
2019
	remove_magicline();

2020
    /* Unpartition the buffer so that it contains all the text again. */
2021
    unpartition_filestruct(&filepart);
2022

2023
    if (old_modified)
2024
2025
2026
2027
	set_modified();

    return retval;
}
2028

2029
#endif /* !NANO_TINY */
2030

2031
/* Write the current file to disk.  If the mark is on, write the current
2032
2033
2034
2035
2036
 * 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
2037
{
2038
    int i;
2039
    bool result = FALSE;
2040
    kind_of_writing_type method = OVERWRITE;
2041
2042
    char *given;
	/* The filename we offer, or what the user typed so far. */
2043
    bool maychange = (openfile->filename[0] == '\0');
2044
	/* Whether it's okay to save the file under a different name. */
2045
#ifndef DISABLE_EXTRA
2046
    static bool did_credits = FALSE;
2047
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2048

2049
    /* Display newlines in filenames as ^J. */
2050
2051
    as_an_at = FALSE;

2052
2053
2054
2055
    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
2056
2057
    }

2058
    given = mallocstrcpy(NULL,
2059
#ifndef NANO_TINY
2060
	(openfile->mark_set && !exiting) ? "" :
2061
#endif
2062
	openfile->filename);
2063
2064
2065

    while (TRUE) {
	const char *msg;
2066
#ifndef NANO_TINY
2067
2068
	const char *formatstr, *backupstr;

2069
	formatstr = (openfile->fmt == DOS_FILE) ? _(" [DOS Format]") :
2070
			(openfile->fmt == MAC_FILE) ? _(" [Mac Format]") : "";
2071

2072
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2073

2074
	/* If we're using restricted mode, don't display the "Write
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2075
2076
2077
	 * Selection to File" prompt.  This function is disabled, since
	 * it allows reading from or writing to files not specified on
	 * the command line. */
2078
	if (openfile->mark_set && !exiting && !ISSET(RESTRICTED))
2079
	    /* TRANSLATORS: The next six strings are prompts. */
2080
2081
	    msg = (method == PREPEND) ? _("Prepend Selection to File") :
			(method == APPEND) ? _("Append Selection to File") :
2082
			_("Write Selection to File");
2083
2084
2085
	else if (method != OVERWRITE)
	    msg = (method == PREPEND) ? _("File Name to Prepend to") :
					_("File Name to Append to");
2086
	else
2087
#endif /* !NANO_TINY */
2088
	    msg = _("File Name to Write");
2089

2090
2091
	present_path = mallocstrcpy(present_path, "./");

2092
2093
	/* If we're using restricted mode, and the filename isn't blank,
	 * disable tab completion. */
2094
	i = do_prompt(!ISSET(RESTRICTED) || openfile->filename[0] == '\0',
2095
		TRUE, MWRITEFILE, given,
2096
#ifndef DISABLE_HISTORIES
2097
2098
		NULL,
#endif
2099
		edit_refresh, "%s%s%s", msg,
2100
2101
#ifndef NANO_TINY
		formatstr, backupstr
2102
#else
2103
		"", ""
2104
2105
2106
#endif
		);

2107
	if (i < 0) {
2108
	    statusbar(_("Cancelled"));
2109
2110
	    break;
	} else {
2111
2112
	    functionptrtype func = func_from_key(&i);

2113
	    /* Upon request, abandon the buffer. */
2114
	    if (func == discard_buffer) {
2115
2116
		free(given);
		return 2;
2117
2118
	    }

2119
	    given = mallocstrcpy(given, answer);
Chris Allegretta's avatar
Chris Allegretta committed
2120

2121
#ifdef ENABLE_BROWSER
2122
	    if (func == to_files_void) {
2123
		char *chosen = do_browse_from(answer);
2124

2125
		if (chosen == NULL)
2126
		    continue;
2127

2128
		/* We have a file now.  Indicate this. */
2129
		free(answer);
2130
		answer = chosen;
2131
	    } else
2132
#endif
2133
#ifndef NANO_TINY
2134
	    if (func == dos_format_void) {
2135
2136
		openfile->fmt = (openfile->fmt == DOS_FILE) ? NIX_FILE :
			DOS_FILE;
2137
		continue;
2138
	    } else if (func == mac_format_void) {
2139
2140
		openfile->fmt = (openfile->fmt == MAC_FILE) ? NIX_FILE :
			MAC_FILE;
2141
		continue;
2142
	    } else if (func == backup_file_void) {
2143
2144
		TOGGLE(BACKUP_FILE);
		continue;
2145
	    } else if (func == prepend_void) {
2146
		method = (method == PREPEND) ? OVERWRITE : PREPEND;
2147
		continue;
2148
	    } else if (func == append_void) {
2149
		method = (method == APPEND) ? OVERWRITE : APPEND;
2150
		continue;
2151
2152
2153
	    } else
#endif /* !NANO_TINY */
	    if (func == do_help_void) {
2154
		continue;
2155
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2156

Chris Allegretta's avatar
Chris Allegretta committed
2157
#ifdef DEBUG
2158
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
2159
#endif
2160

2161
#ifndef DISABLE_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2162
2163
2164
2165
2166
2167
2168
	    /* 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) &&
2169
			strcasecmp(answer, "zzy") == 0) {
2170
		do_credits();
2171
		did_credits = TRUE;
2172
2173
		break;
	    }
2174
#endif
2175

2176
	    if (method == OVERWRITE) {
2177
		bool name_exists, do_warning;
2178
		char *full_answer, *full_filename;
2179
2180
		struct stat st;

2181
2182
		full_answer = get_full_path(answer);
		full_filename = get_full_path(openfile->filename);
2183
2184
		name_exists = (stat((full_answer == NULL) ?
				answer : full_answer, &st) != -1);
2185
2186
2187
2188
		if (openfile->filename[0] == '\0')
		    do_warning = name_exists;
		else
		    do_warning = (strcmp((full_answer == NULL) ?
2189
2190
				answer : full_answer, (full_filename == NULL) ?
				openfile->filename : full_filename) != 0);
2191

2192
2193
		free(full_filename);
		free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2194

2195
		if (do_warning) {
2196
2197
2198
		    /* 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. */
2199
		    if (ISSET(RESTRICTED)) {
2200
			/* TRANSLATORS: Restricted mode forbids overwriting. */
2201
2202
			warn_and_shortly_pause(_("File exists -- "
					"cannot overwrite"));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2203
			continue;
2204
		    }
2205

2206
		    if (!maychange) {
2207
#ifndef NANO_TINY
2208
2209
2210
			if (exiting || !openfile->mark_set)
#endif
			{
2211
2212
			    if (do_yesno_prompt(FALSE, _("Save file under "
					"DIFFERENT NAME? ")) < 1)
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
				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)
2228
2229
			    continue;
		    }
2230
		}
2231
#ifndef NANO_TINY
2232
2233
2234
		/* Complain if the file exists, the name hasn't changed,
		 * and the stat information we had before does not match
		 * what we have now. */
2235
2236
2237
2238
		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)) {
2239

2240
2241
		    warn_and_shortly_pause(_("File on disk has changed"));

2242
2243
		    if (do_yesno_prompt(FALSE, _("File was modified since "
				"you opened it; continue saving? ")) < 1)
2244
2245
			continue;
		}
2246
#endif
2247
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2248

2249
	    /* Here's where we allow the selected text to be written to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2250
2251
2252
	     * 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. */
2253
#ifndef NANO_TINY
2254
	    if (openfile->mark_set && !exiting && !ISSET(RESTRICTED))
2255
		result = write_marked_file(answer, NULL, FALSE, method);
2256
	    else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2257
#endif
2258
		result = write_file(answer, NULL, FALSE, method, FALSE);
2259

2260
2261
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2262
    }
2263

2264
    free(given);
2265

2266
    return result ? 1 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
2267
2268
}

2269
/* Write the current buffer to disk, or discard it. */
2270
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
2271
{
2272
2273
2274
    /* 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
2275
}
Chris Allegretta's avatar
Chris Allegretta committed
2276

2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
#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

2288
2289
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
2290
char *real_dir_from_tilde(const char *buf)
2291
{
2292
    char *retval;
2293

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2294
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2295
	size_t i = 1;
2296
	char *tilde_dir;
2297

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

2302
2303
2304
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
2305
	    tilde_dir = mallocstrcpy(NULL, homedir);
2306
	} else {
2307
#ifdef HAVE_PWD_H
2308
2309
	    const struct passwd *userdata;

2310
2311
2312
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

2313
2314
	    do {
		userdata = getpwent();
2315
2316
	    } while (userdata != NULL &&
			strcmp(userdata->pw_name, tilde_dir + 1) != 0);
2317
	    endpwent();
2318
	    if (userdata != NULL)
2319
		tilde_dir = mallocstrcpy(tilde_dir, userdata->pw_dir);
2320
2321
2322
#else
	    tilde_dir = strdup("");
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2323
	}
2324

2325
2326
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2327

2328
2329
2330
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2331

2332
    return retval;
2333
2334
}

2335
#if defined(ENABLE_TABCOMP) || defined(ENABLE_BROWSER)
2336
/* Our sort routine for file listings.  Sort alphabetically and
2337
 * case-insensitively, and sort directories before filenames. */
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
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;

2351
2352
2353
    /* 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
2354
     * have to use multibyte strcasecmp() instead. */
2355
    return mbstrcasecmp(a, b);
2356
}
2357
2358
2359
2360
2361

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

2365
2366
    for (; len > 0; len--)
	free(array[len - 1]);
2367

2368
2369
    free(array);
}
2370
2371
#endif

2372
#ifdef ENABLE_TABCOMP
2373
/* Is the given path a directory? */
2374
bool is_dir(const char *buf)
2375
{
2376
    char *dirptr;
2377
    struct stat fileinfo;
2378
2379
2380
2381
    bool retval;

    dirptr = real_dir_from_tilde(buf);

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

2384
    free(dirptr);
2385

2386
    return retval;
2387
}
Chris Allegretta's avatar
Chris Allegretta committed
2388

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2389
2390
2391
/* 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
2392
 * file, with the copyright years updated:
Chris Allegretta's avatar
Chris Allegretta committed
2393
2394
2395
 *
 * Termios command line History and Editting, originally
 * intended for NetBSD sh (ash)
2396
 * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
Chris Allegretta's avatar
Chris Allegretta committed
2397
2398
2399
2400
2401
2402
2403
2404
 *      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.
2405
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
2406

2407
/* We consider the first buf_len characters of buf for ~username tab
2408
2409
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
2410
	size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2411
{
2412
    char **matches = NULL;
2413
2414
2415
#ifdef HAVE_PWD_H
    const struct passwd *userdata;
#endif
2416

2417
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2418

2419
    *num_matches = 0;
2420

2421
#ifdef HAVE_PWD_H
2422
    while ((userdata = getpwent()) != NULL) {
2423
	if (strncmp(userdata->pw_name, buf + 1, buf_len - 1) == 0) {
2424
2425
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2426

2427
2428
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2429
	     * directory, in which case just go to the next match. */
2430
	    if (outside_of_confinement(userdata->pw_dir, TRUE))
2431
		continue;
2432
2433
#endif

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2434
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
2435
2436
					sizeof(char *));
	    matches[*num_matches] = charalloc(strlen(userdata->pw_name) + 2);
2437
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2438
	    ++(*num_matches);
2439
	}
2440
2441
    }
    endpwent();
2442
#endif
2443

2444
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2445
2446
}

2447
/* We consider the first buf_len characters of buf for filename tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2448
 * completion. */
2449
char **cwd_tab_completion(const char *buf, bool allow_files, size_t
2450
	*num_matches, size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2451
{
2452
2453
    char *dirname = mallocstrcpy(NULL, buf);
    char *slash, *filename;
2454
2455
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2456
    DIR *dir;
2457
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2458

2459
    *num_matches = 0;
2460
    dirname[buf_len] = '\0';
2461

2462
    /* If there's a / in the name, split out filename and directory parts. */
2463
2464
2465
    slash = strrchr(dirname, '/');
    if (slash != NULL) {
	char *wasdirname = dirname;
2466

2467
2468
	filename = mallocstrcpy(NULL, ++slash);
	/* Cut off the filename part after the slash. */
2469
	*slash = '\0';
2470
	dirname = real_dir_from_tilde(dirname);
2471
2472
2473
2474
2475
2476
	/* 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);
	}
2477
	free(wasdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2478
    } else {
2479
	filename = dirname;
2480
	dirname = mallocstrcpy(NULL, present_path);
Chris Allegretta's avatar
Chris Allegretta committed
2481
2482
    }

2483
    assert(dirname[strlen(dirname) - 1] == '/');
2484

Chris Allegretta's avatar
Chris Allegretta committed
2485
    dir = opendir(dirname);
2486

2487
    if (dir == NULL) {
2488
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2489
	beep();
2490
2491
2492
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2493
    }
2494
2495
2496

    filenamelen = strlen(filename);

2497
    while ((nextdir = readdir(dir)) != NULL) {
2498
2499
	bool skip_match = FALSE;

Chris Allegretta's avatar
Chris Allegretta committed
2500
#ifdef DEBUG
2501
	fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2502
#endif
2503
	/* See if this matches. */
2504
	if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
2505
2506
		(*filename == '.' || (strcmp(nextdir->d_name, ".") != 0 &&
		strcmp(nextdir->d_name, "..") != 0))) {
2507
2508
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2509

2510
	    char *tmp = charalloc(strlen(dirname) + strlen(nextdir->d_name) + 1);
2511
2512
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

2513
2514
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2515
	     * directory, in which case just go to the next match. */
2516
	    skip_match = outside_of_confinement(tmp, TRUE);
2517
2518
2519
2520
#endif

	    /* ...or unless the match isn't a directory and allow_files
	     * isn't set, in which case just go to the next match. */
2521
	    skip_match = skip_match || (!allow_files && !is_dir(tmp));
2522
2523

	    free(tmp);
2524

2525
	    if (skip_match)
2526
		continue;
2527

2528
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
2529
					sizeof(char *));
2530
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2531
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2532
2533
	}
    }
2534

2535
2536
    closedir(dir);
    free(dirname);
2537
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2538

2539
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2540
2541
}

2542
/* Do tab completion.  place refers to how much the statusbar cursor
2543
2544
 * position should be advanced.  refresh_func is the function we will
 * call to refresh the edit window. */
2545
char *input_tab(char *buf, bool allow_files, size_t *place,
2546
	bool *lastwastab, void (*refresh_func)(void), bool *listed)
Chris Allegretta's avatar
Chris Allegretta committed
2547
{
2548
    size_t num_matches = 0, buf_len;
2549
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2550

2551
    assert(buf != NULL && place != NULL && *place <= strlen(buf) &&
2552
2553
2554
		lastwastab != NULL && refresh_func != NULL && listed != NULL);

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

2556
2557
    /* 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
2558
    if (*place > 0 && *buf == '~') {
2559
	const char *slash = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2560

2561
	if (slash == NULL || slash >= buf + *place)
2562
	    matches = username_tab_completion(buf, &num_matches, *place);
2563
    }
2564

2565
2566
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
2567
	matches = cwd_tab_completion(buf, allow_files, &num_matches, *place);
2568

2569
2570
2571
    buf_len = strlen(buf);

    if (num_matches == 0 || *place != buf_len)
2572
2573
2574
	beep();
    else {
	size_t match, common_len = 0;
2575
	char *mzero, *glued;
2576
	const char *lastslash = revstrstr(buf, "/", buf + *place);
2577
	size_t lastslash_len = (lastslash == NULL) ? 0 : lastslash - buf + 1;
2578
	char char1[MAXCHARLEN], char2[MAXCHARLEN];
2579
	int len1, len2;
2580

2581
	/* Get the number of characters that all matches have in common. */
2582
	while (TRUE) {
2583
	    len1 = parse_mbchar(matches[0] + common_len, char1, NULL);
2584
2585

	    for (match = 1; match < num_matches; match++) {
2586
2587
2588
		len2 = parse_mbchar(matches[match] + common_len, char2, NULL);

		if (len1 != len2 || strncmp(char1, char2, len2) != 0)
2589
2590
		    break;
	    }
2591

2592
	    if (match < num_matches || matches[0][common_len] == '\0')
2593
		break;
2594

2595
	    common_len += len1;
2596
	}
2597

2598
	mzero = charalloc(lastslash_len + common_len + 1);
2599
2600
2601

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

2603
	common_len += lastslash_len;
2604
	mzero[common_len] = '\0';
2605

2606
2607
2608
2609
	/* 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);

2610
	assert(common_len >= *place);
2611

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

2615
2616
	    assert(common_len > *place);
	}
2617

2618
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2619
	    beep();
2620

2621
	/* If the matches have something in common, show that part. */
2622
	if (common_len != *place) {
2623
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
2624
	    charmove(buf + common_len, buf + *place, buf_len - *place + 1);
2625
	    strncpy(buf, mzero, common_len);
2626
	    *place = common_len;
2627
2628
2629
	}

	if (!*lastwastab)
2630
	    *lastwastab = TRUE;
2631
	else if (num_matches > 1) {
2632
	    int longest_name = 0, ncols, editline = 0;
2633

2634
	    /* Sort the list of available choices. */
2635
2636
	    qsort(matches, num_matches, sizeof(char *), diralphasort);

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

2641
2642
		if (namelen > longest_name)
		    longest_name = namelen;
2643
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2644

2645
2646
	    if (longest_name > COLS - 1)
		longest_name = COLS - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2647

2648
	    /* Each column will be (longest_name + 2) columns wide, i.e.
2649
2650
	     * two spaces between columns, except that there will be
	     * only one space after the last column. */
2651
	    ncols = (COLS + 1) / (longest_name + 2);
2652

2653
	    /* Blank the edit window and hide the cursor. */
2654
2655
	    blank_edit();
	    curs_set(0);
2656
	    wmove(edit, 0, 0);
2657

2658
	    /* Now print the list of matches out there. */
2659
2660
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2661

2662
		wmove(edit, editline, (longest_name + 2) * (match % ncols));
2663

2664
		if (match % ncols == 0 && editline == editwinrows - 1 &&
2665
			num_matches - match > ncols) {
2666
2667
2668
		    waddstr(edit, _("(more)"));
		    break;
		}
2669

2670
		disp = display_string(matches[match], 0, longest_name, FALSE);
2671
2672
		waddstr(edit, disp);
		free(disp);
2673

2674
		if ((match + 1) % ncols == 0)
2675
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2676
	    }
2677

2678
	    wnoutrefresh(edit);
2679
	    *listed = TRUE;
2680
2681
	}

2682
	free(glued);
2683
	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2684
2685
    }

2686
    free_chararray(matches, num_matches);
2687

2688
2689
2690
    /* 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)
2691
2692
	refresh_func();

2693
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2694
}
2695
#endif /* ENABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2696

2697
2698
/* Return the filename part of the given path. */
const char *tail(const char *path)
2699
{
2700
    const char *slash = strrchr(path, '/');
2701

2702
2703
    if (slash == NULL)
	return path;
2704
    else
2705
	return ++slash;
2706
2707
}

2708
2709
#ifndef DISABLE_HISTORIES
/* Return the constructed dirfile path, or NULL if we can't find the home
2710
 * directory.  The string is dynamically allocated, and should be freed. */
2711
char *construct_filename(const char *str)
2712
{
2713
    char *newstr = NULL;
2714

2715
2716
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2717

2718
2719
2720
	newstr = charalloc(homelen + strlen(str) + 1);
	strcpy(newstr, homedir);
	strcpy(newstr + homelen, str);
Chris Allegretta's avatar
Chris Allegretta committed
2721
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2722

2723
2724
2725
2726
2727
    return newstr;
}

char *histfilename(void)
{
2728
    return construct_filename("/.nano/search_history");
2729
2730
}

2731
2732
/* Construct the legacy history filename. */
/* (To be removed in 2018.) */
2733
2734
2735
2736
2737
2738
2739
char *legacyhistfilename(void)
{
    return construct_filename("/.nano_history");
}

char *poshistfilename(void)
{
2740
    return construct_filename("/.nano/filepos_history");
2741
2742
2743
2744
}

void history_error(const char *msg, ...)
{
2745
    va_list ap;
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755

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

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

2756
2757
2758
/* 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. */
2759
2760
int check_dotnano(void)
{
2761
    int ret = 1;
2762
2763
2764
2765
2766
    struct stat dirstat;
    char *nanodir = construct_filename("/.nano");

    if (stat(nanodir, &dirstat) == -1) {
	if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
2767
	    history_error(N_("Unable to create directory %s: %s\n"
2768
2769
				"It is required for saving/loading "
				"search history or cursor positions.\n"),
2770
				nanodir, strerror(errno));
2771
	    ret = 0;
2772
2773
	}
    } else if (!S_ISDIR(dirstat.st_mode)) {
2774
	history_error(N_("Path %s is not a directory and needs to be.\n"
2775
2776
				"Nano will be unable to load or save "
				"search history or cursor positions.\n"),
2777
				nanodir);
2778
	ret = 0;
2779
    }
2780
2781
2782

    free(nanodir);
    return ret;
2783
2784
}

2785
/* Load the search and replace histories from ~/.nano/search_history. */
2786
2787
2788
void load_history(void)
{
    char *nanohist = histfilename();
2789
2790
2791
    char *legacyhist = legacyhistfilename();
    struct stat hstat;

2792
2793
    /* If there is an old history file, migrate it. */
    /* (To be removed in 2018.) */
2794
    if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
2795
	if (rename(legacyhist, nanohist) == -1)
2796
2797
2798
	    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));
2799
	else
2800
2801
2802
	    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);
2803
2804
    }

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

2809
	if (hist == NULL) {
2810
	    if (errno != ENOENT) {
2811
2812
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2813
		history_error(N_("Error reading %s: %s"), nanohist,
2814
			strerror(errno));
2815
	    }
2816
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2817
2818
2819
	    /* 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. */
2820
	    filestruct **history = &search_history;
2821
	    char *line = NULL;
2822
	    size_t buf_len = 0;
2823
2824
	    ssize_t read;

2825
2826
	    while ((read = getline(&line, &buf_len, hist)) > 0) {
		line[--read] = '\0';
2827
2828
2829
		if (read > 0) {
		    /* Encode any embedded NUL as 0x0A. */
		    unsunder(line, read);
2830
		    update_history(history, line);
2831
		} else
2832
2833
		    history = &replace_history;
	    }
2834

2835
	    fclose(hist);
2836
	    free(line);
2837
	}
2838
	free(nanohist);
2839
	free(legacyhist);
2840
2841
2842
    }
}

2843
/* Write the lines of a history list, starting with the line at head, to
2844
2845
 * the open file at hist.  Return TRUE if the write succeeded, and FALSE
 * otherwise. */
2846
bool writehist(FILE *hist, const filestruct *head)
2847
{
2848
    const filestruct *item;
2849

2850
    /* Write a history list, from the oldest item to the newest. */
2851
2852
    for (item = head; item != NULL; item = item->next) {
	size_t length = strlen(item->data);
2853

2854
2855
2856
	/* Decode 0x0A bytes as embedded NULs. */
	sunder(item->data);

2857
2858
2859
	if (fwrite(item->data, sizeof(char), length, hist) < length)
	    return FALSE;
	if (putc('\n', hist) == EOF)
2860
	    return FALSE;
2861
    }
2862

2863
    return TRUE;
2864
2865
}

2866
/* Save the search and replace histories to ~/.nano/search_history. */
2867
2868
void save_history(void)
{
2869
    char *nanohist;
2870

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2871
    /* Don't save unchanged or empty histories. */
2872
    if (!history_has_changed() || (searchbot->lineno == 1 &&
2873
		replacebot->lineno == 1))
2874
2875
	return;

2876
2877
2878
2879
    nanohist = histfilename();

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

2881
	if (hist == NULL)
2882
2883
	    fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
			strerror(errno));
2884
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2885
2886
	    /* Make sure no one else can read from or write to the
	     * history file. */
2887
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2888

2889
	    if (!writehist(hist, searchage) || !writehist(hist, replaceage))
2890
		fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
2891
			strerror(errno));
2892

2893
2894
	    fclose(hist);
	}
2895

2896
2897
2898
	free(nanohist);
    }
}
2899

2900
/* Save the recorded last file positions to ~/.nano/filepos_history. */
2901
2902
void save_poshistory(void)
{
2903
    char *poshist = poshistfilename();
2904
    poshiststruct *posptr;
2905
    FILE *hist;
2906

2907
2908
    if (poshist == NULL)
	return;
2909

2910
    hist = fopen(poshist, "wb");
2911

2912
2913
2914
2915
2916
    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);
2917

2918
	for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2919
	    char *path_and_place;
2920
2921
	    size_t length;

2922
2923
	    /* Assume 20 decimal positions each for line and column number,
	     * plus two spaces, plus the line feed, plus the null byte. */
2924
2925
	    path_and_place = charalloc(strlen(posptr->filename) + 44);
	    sprintf(path_and_place, "%s %ld %ld\n", posptr->filename,
2926
			(long)posptr->lineno, (long)posptr->xno);
2927
	    length = strlen(path_and_place);
2928
2929

	    /* Encode newlines in filenames as nulls. */
2930
	    sunder(path_and_place);
2931
	    /* Restore the terminating newline. */
2932
	    path_and_place[length - 1] = '\n';
2933

2934
	    if (fwrite(path_and_place, sizeof(char), length, hist) < length)
2935
2936
		fprintf(stderr, _("Error writing %s: %s\n"),
					poshist, strerror(errno));
2937
	    free(path_and_place);
2938
	}
2939
	fclose(hist);
2940
    }
2941
    free(poshist);
2942
2943
}

2944
2945
/* 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. */
2946
2947
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
2948
    poshiststruct *posptr, *theone, *posprev = NULL;
2949
    char *fullpath = get_full_path(filename);
2950

2951
    if (fullpath == NULL || fullpath[strlen(fullpath) - 1] == '/' || inhelp) {
2952
	free(fullpath);
2953
	return;
2954
    }
2955

2956
    /* Look for a matching filename in the list. */
2957
    for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2958
2959
	if (!strcmp(posptr->filename, fullpath))
	    break;
2960
2961
2962
	posprev = posptr;
    }

2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
    /* 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;
    }

2977
    theone = posptr;
2978

2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
    /* 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) {
2989
2990
2991
2992
	if (posprev == NULL)
	    position_history = posptr->next;
	else
	    posprev->next = posptr->next;
2993
2994
2995
2996
2997
2998
2999
3000
3001
	while (posptr->next != NULL)
	    posptr = posptr->next;
	posptr->next = theone;
    }

    /* Store the last cursor position. */
    theone->lineno = lineno;
    theone->xno = xpos;
    theone->next = NULL;
3002
3003
3004
3005

    free(fullpath);
}

3006
3007
3008
3009
/* 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)
3010
{
3011
    poshiststruct *posptr = position_history;
3012
3013
3014
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
3015
3016
3017
3018
	return FALSE;

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

3020
    free(fullpath);
3021
3022

    if (posptr == NULL)
3023
	return FALSE;
3024
3025
3026
3027

    *line = posptr->lineno;
    *column = posptr->xno;
    return TRUE;
3028
3029
}

3030
/* Load the recorded file positions from ~/.nano/filepos_history. */
3031
3032
void load_poshistory(void)
{
3033
    char *poshist = poshistfilename();
3034
    FILE *hist;
3035

3036
3037
3038
    /* If the home directory is missing, do_rcfile() will have reported it. */
    if (poshist == NULL)
	return;
3039

3040
    hist = fopen(poshist, "rb");
3041

3042
3043
    if (hist == NULL) {
	if (errno != ENOENT) {
3044
	    /* When reading failed, don't save history when we quit. */
3045
3046
	    UNSET(POS_HISTORY);
	    history_error(N_("Error reading %s: %s"), poshist, strerror(errno));
3047
	}
3048
3049
3050
    } else {
	char *line = NULL, *lineptr, *xptr;
	size_t buf_len = 0;
3051
	ssize_t read, count = 0;
3052
3053
3054
	poshiststruct *record_ptr = NULL, *newrecord;

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

3059
3060
	    /* Find where the x index and line number are in the line. */
	    xptr = revstrstr(line, " ", line + read - 3);
3061
3062
	    if (xptr == NULL)
		continue;
3063
	    lineptr = revstrstr(line, " ", xptr - 2);
3064
3065
	    if (lineptr == NULL)
		continue;
3066
3067
3068
3069

	    /* Now separate the three elements of the line. */
	    *(xptr++) = '\0';
	    *(lineptr++) = '\0';
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084

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

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

3090
		position_history = position_history->next;
3091
3092
3093
3094

		free(drop_record->filename);
		free(drop_record);
	    }
3095
3096
3097
	}
	fclose(hist);
	free(line);
3098
    }
3099
    free(poshist);
3100
}
3101
#endif /* !DISABLE_HISTORIES */