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

22
#include "proto.h"
23

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

33
34
#define LOCKBUFSIZE 8192

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

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

57
    free(namecopy);
58

59
    return validity;
60
61
}

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

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

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

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

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

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

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

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

99
    openfile->fmt = NIX_FILE;
100

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

336
337
	close(lockfd);

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

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

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

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

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

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

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

    retval = write_lockfile(lockfilename, filename, FALSE);

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

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

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

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

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

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

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

441
442
    realname = real_dir_from_tilde(filename);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    if (descriptor < 0)
	return;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    if (ISSET(VIEW_MODE))
	return TRUE;

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

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

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

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

    return result;
717
718
}

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

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

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

765
    buf = charalloc(bufx);
766

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

889
    free(buf);
890

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

996
997
    free(full_filename);

998
999
1000
    return fd;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1188
		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
1414
1415
void init_operating_dir(void)
{
    full_operating_dir = get_full_path(operating_dir);

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

1420
    snuggly_fit(&full_operating_dir);
1421
1422
}

1423
1424
1425
1426
1427
/* Check to see if we're inside the operating directory.  Return FALSE
 * if we are, or TRUE otherwise.  If allow_tabcomp is TRUE, allow
 * incomplete names that would be matches for the operating directory,
 * so that tab completion will work. */
bool check_operating_dir(const char *currpath, bool allow_tabcomp)
1428
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1429
1430
1431
1432
    /* full_operating_dir is global for memory cleanup.  It should have
     * already been initialized by init_operating_dir().  Also, a
     * relative operating directory path will only be handled properly
     * if this is done. */
1433

1434
    char *fullpath;
1435
    bool retval = FALSE;
1436
    const char *whereami1, *whereami2 = NULL;
1437

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

1442
    fullpath = get_full_path(currpath);
1443

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

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

1458
    /* If both searches failed, we're outside the operating directory.
1459
     * Otherwise, check the search results.  If the full operating
1460
1461
1462
     * directory path is not at the beginning of the full current path
     * (for normal usage) and vice versa (for tab completion, if we're
     * allowing it), we're outside the operating directory. */
1463
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1464
1465
	retval = TRUE;
    free(fullpath);
1466
1467

    /* Otherwise, we're still inside it. */
1468
    return retval;
1469
}
1470
1471
#endif

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

    return response;
1488
1489
}

1490
1491
/* Transform the specified backup directory to an absolute path,
 * and verify that it is usable. */
1492
1493
void init_backup_dir(void)
{
1494
1495
1496
1497
1498
1499
1500
1501
    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);
1502
}
1503
#endif /* !NANO_TINY */
1504

1505
/* Read from inn, write to out.  We assume inn is opened for reading,
1506
 * and out for writing.  We return 0 on success, -1 on read error, or -2
1507
1508
1509
 * 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)
1510
{
1511
    int retval = 0;
1512
    char buf[BUFSIZ];
1513
    size_t charsread;
1514
    int (*flush_out_fnc)(FILE *) = (close_out) ? fclose : fflush;
1515

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

1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
    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
1529

1530
1531
    if (fclose(inn) == EOF)
	retval = -1;
1532
    if (flush_out_fnc(out) == EOF)
1533
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1534

1535
1536
1537
    return retval;
}

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1580
    if (*name == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1581
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1582

1583
1584
    if (!tmp)
	titlebar(NULL);
1585

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1586
    realname = real_dir_from_tilde(name);
1587

1588
#ifndef DISABLE_OPERATINGDIR
1589
    /* If we're writing a temporary file, we're probably going outside
1590
     * the operating directory, so skip the operating directory test. */
1591
    if (!tmp && check_operating_dir(realname, FALSE)) {
1592
	statusline(ALERT, _("Can't write outside of %s"), full_operating_dir);
1593
	goto cleanup_and_exit;
1594
1595
1596
    }
#endif

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

1601
#ifndef NANO_TINY
1602
    /* Check whether the file (at the end of the symlink) exists. */
1603
    realexists = (stat(realname, &st) != -1);
1604

1605
1606
1607
1608
    /* 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. */
1609
1610
    if (openfile->current_stat == NULL && !tmp && realexists)
	stat_with_alloc(realname, &openfile->current_stat);
1611

1612
    /* We backup only if the backup toggle is set, the file isn't
1613
1614
1615
1616
     * 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. */
1617
    if (ISSET(BACKUP_FILE) && !tmp && realexists && openfile->current_stat &&
1618
		(method != OVERWRITE || openfile->mark_set ||
1619
		openfile->current_stat->st_mtime == st.st_mtime)) {
1620
	int backup_fd;
1621
	FILE *backup_file;
1622
	char *backupname;
1623
	static struct timespec filetime[2];
1624
	int backup_cflags;
1625

1626
	/* Save the original file's access and modification times. */
1627
1628
	filetime[0].tv_sec = openfile->current_stat->st_atime;
	filetime[1].tv_sec = openfile->current_stat->st_mtime;
1629

1630
1631
1632
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1633

1634
	    if (f == NULL) {
1635
		statusline(ALERT, _("Error reading %s: %s"), realname,
1636
			strerror(errno));
1637
1638
1639
1640
		/* 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;
1641
	    }
1642
1643
	}

1644
	/* If backup_dir is set, we set backupname to
1645
1646
1647
1648
	 * 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]. */
1649
	if (backup_dir != NULL) {
1650
	    char *backuptemp = get_full_path(realname);
1651

1652
	    if (backuptemp == NULL)
1653
1654
1655
1656
1657
1658
		/* 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~. */
1659
		backuptemp = mallocstrcpy(NULL, tail(realname));
1660
	    else {
1661
1662
		size_t i = 0;

1663
1664
1665
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1666
1667
1668
		}
	    }

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

1693
	/* First, unlink any existing backups.  Next, open the backup
1694
1695
	 * file with O_CREAT and O_EXCL.  If it succeeds, we have a file
	 * descriptor to a new backup file. */
1696
	if (unlink(backupname) < 0 && errno != ENOENT && !ISSET(INSECURE_BACKUP)) {
1697
1698
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1699
1700
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1701
1702
1703
1704
	    free(backupname);
	    goto cleanup_and_exit;
	}

1705
1706
	if (ISSET(INSECURE_BACKUP))
	    backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
1707
	else
1708
1709
1710
	    backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

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

1716
	if (backup_fd < 0 || backup_file == NULL) {
1717
1718
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1719
	    free(backupname);
1720
1721
1722
1723
	    /* 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! */
1724
1725
	    goto cleanup_and_exit;
	}
1726

1727
1728
1729
1730
1731
1732
	/* 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);
1733
1734
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1735
1736
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1737
1738
1739
1740
	    free(backupname);
	    goto cleanup_and_exit;
	}

1741
1742
1743
1744
	/* Set the backup's mode bits. */
	if (fchmod(backup_fd, openfile->current_stat->st_mode) == -1 &&
			!ISSET(INSECURE_BACKUP)) {
	    fclose(backup_file);
1745
1746
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1747
1748
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1749
	    free(backupname);
1750
	    goto cleanup_and_exit;
1751
1752
1753
	}

#ifdef DEBUG
1754
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1755
1756
#endif

1757
	/* Copy the file. */
1758
1759
	if (copy_file(f, backup_file, FALSE) != 0) {
	    fclose(backup_file);
1760
	    statusline(ALERT, _("Error reading %s: %s"), realname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1761
			strerror(errno));
1762
1763
1764
	    goto cleanup_and_exit;
	}

1765
	/* And set the backup's timestamps. */
1766
1767
	if (futimens(backup_fd, filetime) == -1 && !ISSET(INSECURE_BACKUP)) {
	    fclose(backup_file);
1768
1769
	    if (prompt_failed_backupwrite(backupname))
		goto skip_backup;
1770
1771
	    statusline(HUSH, _("Error writing backup file %s: %s"),
			backupname, strerror(errno));
1772
	    goto cleanup_and_exit;
1773
	}
1774

1775
	fclose(backup_file);
1776
1777
	free(backupname);
    }
1778

1779
    skip_backup:
1780
#endif /* !NANO_TINY */
1781

1782
1783
    if (f_open == NULL) {
	original_umask = umask(0);
1784

1785
	/* If we create a temp file, we don't let anyone else access it.
1786
1787
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1788
	    umask(S_IRWXG | S_IRWXO);
1789
1790
	else
	    umask(original_umask);
1791
    }
1792

1793
    /* If we're prepending, copy the file to a temp file. */
1794
    if (method == PREPEND) {
1795
1796
1797
	int fd_source;
	FILE *f_source = NULL;

1798
1799
1800
1801
	if (f == NULL) {
	    f = fopen(realname, "rb");

	    if (f == NULL) {
1802
		statusline(ALERT, _("Error reading %s: %s"), realname,
1803
1804
1805
1806
1807
			strerror(errno));
		goto cleanup_and_exit;
	    }
	}

1808
	tempname = safe_tempfile(&f);
1809

1810
	if (tempname == NULL) {
1811
	    statusline(ALERT, _("Error writing temp file: %s"),
1812
			strerror(errno));
1813
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1814
	}
1815

1816
	if (f_open == NULL) {
1817
	    fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1818

1819
1820
1821
	    if (fd_source != -1) {
		f_source = fdopen(fd_source, "rb");
		if (f_source == NULL) {
1822
		    statusline(ALERT, _("Error reading %s: %s"), realname,
1823
				strerror(errno));
1824
1825
1826
1827
1828
1829
		    close(fd_source);
		    fclose(f);
		    unlink(tempname);
		    goto cleanup_and_exit;
		}
	    }
1830
1831
	}

1832
	if (f_source == NULL || copy_file(f_source, f, TRUE) != 0) {
1833
	    statusline(ALERT, _("Error writing temp file: %s"),
1834
			strerror(errno));
1835
	    unlink(tempname);
1836
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1837
1838
1839
	}
    }

1840
1841
1842
    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*. */
1843
	fd = open(realname, O_WRONLY | O_CREAT | ((method == APPEND) ?
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1844
1845
		O_APPEND : (tmp ? O_EXCL : O_TRUNC)), S_IRUSR |
		S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1846

1847
1848
	/* Set the umask back to the user's original value. */
	umask(original_umask);
1849

1850
1851
	/* If we couldn't open the file, give up. */
	if (fd == -1) {
1852
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1853
			strerror(errno));
1854
1855
1856
1857
	    if (tempname != NULL)
		unlink(tempname);
	    goto cleanup_and_exit;
	}
1858

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

1861
	if (f == NULL) {
1862
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1863
			strerror(errno));
1864
1865
1866
	    close(fd);
	    goto cleanup_and_exit;
	}
1867
1868
    }

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

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

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

1877
1878
	/* Convert nulls to newlines.  data_len is the string's real
	 * length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1879
1880
	unsunder(fileptr->data, data_len);

1881
	if (size < data_len) {
1882
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1883
			strerror(errno));
1884
	    fclose(f);
1885
	    goto cleanup_and_exit;
1886
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1887

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

1906
	    if (openfile->fmt != MAC_FILE)
1907
#endif
1908
		if (putc('\n', f) == EOF) {
1909
		    statusline(ALERT, _("Error writing %s: %s"), realname,
1910
				strerror(errno));
1911
1912
1913
1914
		    fclose(f);
		    goto cleanup_and_exit;
		}
	}
Chris Allegretta's avatar
Chris Allegretta committed
1915
1916
1917
1918
1919

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

1920
    /* If we're prepending, open the temp file, and append it to f. */
1921
    if (method == PREPEND) {
1922
1923
1924
	int fd_source;
	FILE *f_source = NULL;

1925
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1926

1927
1928
1929
1930
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1931
	}
1932

1933
	if (f_source == NULL) {
1934
1935
	    statusline(ALERT, _("Error reading %s: %s"), tempname,
			strerror(errno));
1936
	    fclose(f);
1937
	    goto cleanup_and_exit;
1938
1939
	}

1940
	if (copy_file(f_source, f, TRUE) != 0) {
1941
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1942
			strerror(errno));
1943
	    goto cleanup_and_exit;
1944
	}
1945
1946

	unlink(tempname);
1947
    } else if (fclose(f) != 0) {
1948
	statusline(ALERT, _("Error writing %s: %s"), realname,
1949
			strerror(errno));
1950
	goto cleanup_and_exit;
1951
    }
1952

1953
    if (method == OVERWRITE && !tmp) {
1954
1955
	/* If we must set the filename, and it changed, adjust things. */
	if (!nonamechange && strcmp(openfile->filename, realname) != 0) {
1956
#ifndef DISABLE_COLOR
1957
	    char *newname;
1958
	    char *oldname = openfile->syntax ? openfile->syntax->name : "";
1959
1960
1961
	    filestruct *line = openfile->fileage;
#endif
	    openfile->filename = mallocstrcpy(openfile->filename, realname);
1962

1963
1964
#ifndef DISABLE_COLOR
	    /* See if the applicable syntax has changed. */
1965
	    color_update();
1966
	    color_init();
1967

1968
	    newname = openfile->syntax ? openfile->syntax->name : "";
1969

1970
	    /* If the syntax changed, discard and recompute the multidata. */
1971
	    if (strcmp(oldname, newname) != 0) {
1972
1973
1974
1975
1976
		for (; line != NULL; line = line->next) {
		    free(line->multidata);
		    line->multidata = NULL;
		}
		precalc_multicolorinfo();
1977
		refresh_needed = TRUE;
1978
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1979
1980
#endif
	}
1981

1982
#ifndef NANO_TINY
1983
	if (!openfile->mark_set)
1984
1985
	    /* Get or update the stat info to reflect the current state. */
	    stat_with_alloc(realname, &openfile->current_stat);
1986
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1987

1988
1989
	statusline(HUSH, P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten), (unsigned long)lineswritten);
1990
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1991
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1992
    }
1993

1994
    retval = TRUE;
1995
1996
1997

  cleanup_and_exit:
    free(realname);
1998
    free(tempname);
1999

2000
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
2001
2002
}

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

2017
    /* Partition the buffer so that it contains only the marked text. */
2018
    mark_order((const filestruct **)&top, &top_x,
2019
		(const filestruct **)&bot, &bot_x, NULL);
2020
    filepart = partition_filestruct(top, top_x, bot, bot_x);
2021

2022
2023
2024
    /* 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') {
2025
	new_magicline();
2026
2027
	added_magicline = TRUE;
    }
2028

2029
    retval = write_file(name, f_open, tmp, method, TRUE);
2030

2031
2032
    /* If we added a magicline, remove it now. */
    if (added_magicline)
2033
2034
	remove_magicline();

2035
    /* Unpartition the buffer so that it contains all the text again. */
2036
    unpartition_filestruct(&filepart);
2037

2038
    if (old_modified)
2039
2040
2041
2042
	set_modified();

    return retval;
}
2043

2044
#endif /* !NANO_TINY */
2045

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

2064
    /* Display newlines in filenames as ^J. */
2065
2066
    as_an_at = FALSE;

2067
2068
2069
2070
    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
2071
2072
    }

2073
    given = mallocstrcpy(NULL,
2074
#ifndef NANO_TINY
2075
	(openfile->mark_set && !exiting) ? "" :
2076
#endif
2077
	openfile->filename);
2078
2079
2080

    while (TRUE) {
	const char *msg;
2081
#ifndef NANO_TINY
2082
2083
	const char *formatstr, *backupstr;

2084
	formatstr = (openfile->fmt == DOS_FILE) ? _(" [DOS Format]") :
2085
			(openfile->fmt == MAC_FILE) ? _(" [Mac Format]") : "";
2086

2087
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2088

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

2105
2106
	present_path = mallocstrcpy(present_path, "./");

2107
2108
	/* If we're using restricted mode, and the filename isn't blank,
	 * disable tab completion. */
2109
	i = do_prompt(!ISSET(RESTRICTED) || openfile->filename[0] == '\0',
2110
		TRUE, MWRITEFILE, given,
2111
#ifndef DISABLE_HISTORIES
2112
2113
		NULL,
#endif
2114
		edit_refresh, "%s%s%s", msg,
2115
2116
#ifndef NANO_TINY
		formatstr, backupstr
2117
#else
2118
		"", ""
2119
2120
2121
#endif
		);

2122
	if (i < 0) {
2123
	    statusbar(_("Cancelled"));
2124
2125
	    break;
	} else {
2126
2127
	    functionptrtype func = func_from_key(&i);

2128
	    /* Upon request, abandon the buffer. */
2129
	    if (func == discard_buffer) {
2130
2131
		free(given);
		return 2;
2132
2133
	    }

2134
	    given = mallocstrcpy(given, answer);
Chris Allegretta's avatar
Chris Allegretta committed
2135

2136
#ifdef ENABLE_BROWSER
2137
	    if (func == to_files_void) {
2138
		char *chosen = do_browse_from(answer);
2139

2140
		if (chosen == NULL)
2141
		    continue;
2142

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

Chris Allegretta's avatar
Chris Allegretta committed
2172
#ifdef DEBUG
2173
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
2174
#endif
2175

2176
#ifndef DISABLE_EXTRA
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2177
2178
2179
2180
2181
2182
2183
	    /* 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) &&
2184
			strcasecmp(answer, "zzy") == 0) {
2185
		do_credits();
2186
		did_credits = TRUE;
2187
2188
		break;
	    }
2189
#endif
2190

2191
	    if (method == OVERWRITE) {
2192
		bool name_exists, do_warning;
2193
		char *full_answer, *full_filename;
2194
2195
		struct stat st;

2196
2197
		full_answer = get_full_path(answer);
		full_filename = get_full_path(openfile->filename);
2198
2199
		name_exists = (stat((full_answer == NULL) ?
				answer : full_answer, &st) != -1);
2200
2201
2202
2203
		if (openfile->filename[0] == '\0')
		    do_warning = name_exists;
		else
		    do_warning = (strcmp((full_answer == NULL) ?
2204
2205
				answer : full_answer, (full_filename == NULL) ?
				openfile->filename : full_filename) != 0);
2206

2207
2208
		free(full_filename);
		free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2209

2210
		if (do_warning) {
2211
2212
2213
		    /* 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. */
2214
		    if (ISSET(RESTRICTED)) {
2215
			/* TRANSLATORS: Restricted mode forbids overwriting. */
2216
2217
			warn_and_shortly_pause(_("File exists -- "
					"cannot overwrite"));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2218
			continue;
2219
		    }
2220

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

2255
2256
		    warn_and_shortly_pause(_("File on disk has changed"));

2257
2258
		    if (do_yesno_prompt(FALSE, _("File was modified since "
				"you opened it; continue saving? ")) < 1)
2259
2260
			continue;
		}
2261
#endif
2262
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2263

2264
	    /* Here's where we allow the selected text to be written to
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2265
2266
2267
	     * 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. */
2268
#ifndef NANO_TINY
2269
	    if (openfile->mark_set && !exiting && !ISSET(RESTRICTED))
2270
		result = write_marked_file(answer, NULL, FALSE, method);
2271
	    else
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2272
#endif
2273
		result = write_file(answer, NULL, FALSE, method, FALSE);
2274

2275
2276
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2277
    }
2278

2279
    free(given);
2280

2281
    return result ? 1 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
2282
2283
}

2284
/* Write the current buffer to disk, or discard it. */
2285
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
2286
{
2287
2288
2289
    /* 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
2290
}
Chris Allegretta's avatar
Chris Allegretta committed
2291

2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
#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

2303
2304
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
2305
char *real_dir_from_tilde(const char *buf)
2306
{
2307
    char *retval;
2308

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2309
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2310
	size_t i = 1;
2311
	char *tilde_dir;
2312

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

2317
2318
2319
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
2320
	    tilde_dir = mallocstrcpy(NULL, homedir);
2321
	} else {
2322
#ifdef HAVE_PWD_H
2323
2324
	    const struct passwd *userdata;

2325
2326
2327
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

2328
2329
	    do {
		userdata = getpwent();
2330
2331
	    } while (userdata != NULL &&
			strcmp(userdata->pw_name, tilde_dir + 1) != 0);
2332
	    endpwent();
2333
	    if (userdata != NULL)
2334
		tilde_dir = mallocstrcpy(tilde_dir, userdata->pw_dir);
2335
2336
2337
#else
	    tilde_dir = strdup("");
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2338
	}
2339

2340
2341
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2342

2343
2344
2345
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2346

2347
    return retval;
2348
2349
}

2350
#if defined(ENABLE_TABCOMP) || defined(ENABLE_BROWSER)
2351
/* Our sort routine for file listings.  Sort alphabetically and
2352
 * case-insensitively, and sort directories before filenames. */
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
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;

2366
2367
2368
    /* 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
2369
     * have to use multibyte strcasecmp() instead. */
2370
    return mbstrcasecmp(a, b);
2371
}
2372
2373
2374
2375
2376

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

2380
2381
    for (; len > 0; len--)
	free(array[len - 1]);
2382

2383
2384
    free(array);
}
2385
2386
#endif

2387
#ifdef ENABLE_TABCOMP
2388
/* Is the given path a directory? */
2389
bool is_dir(const char *buf)
2390
{
2391
    char *dirptr;
2392
    struct stat fileinfo;
2393
2394
2395
2396
    bool retval;

    dirptr = real_dir_from_tilde(buf);

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

2399
    free(dirptr);
2400

2401
    return retval;
2402
}
Chris Allegretta's avatar
Chris Allegretta committed
2403

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

2422
/* We consider the first buf_len characters of buf for ~username tab
2423
2424
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
2425
	size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2426
{
2427
    char **matches = NULL;
2428
2429
2430
#ifdef HAVE_PWD_H
    const struct passwd *userdata;
#endif
2431

2432
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2433

2434
    *num_matches = 0;
2435

2436
#ifdef HAVE_PWD_H
2437
    while ((userdata = getpwent()) != NULL) {
2438
	if (strncmp(userdata->pw_name, buf + 1, buf_len - 1) == 0) {
2439
2440
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2441

2442
2443
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2444
2445
2446
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2447
2448
#endif

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2449
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
2450
2451
					sizeof(char *));
	    matches[*num_matches] = charalloc(strlen(userdata->pw_name) + 2);
2452
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2453
	    ++(*num_matches);
2454
	}
2455
2456
    }
    endpwent();
2457
#endif
2458

2459
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2460
2461
}

2462
/* We consider the first buf_len characters of buf for filename tab
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2463
 * completion. */
2464
char **cwd_tab_completion(const char *buf, bool allow_files, size_t
2465
	*num_matches, size_t buf_len)
Chris Allegretta's avatar
Chris Allegretta committed
2466
{
2467
2468
    char *dirname = mallocstrcpy(NULL, buf);
    char *slash, *filename;
2469
2470
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2471
    DIR *dir;
2472
    const struct dirent *nextdir;
Chris Allegretta's avatar
Chris Allegretta committed
2473

2474
    *num_matches = 0;
2475
    dirname[buf_len] = '\0';
2476

2477
    /* If there's a / in the name, split out filename and directory parts. */
2478
2479
2480
    slash = strrchr(dirname, '/');
    if (slash != NULL) {
	char *wasdirname = dirname;
2481

2482
2483
	filename = mallocstrcpy(NULL, ++slash);
	/* Cut off the filename part after the slash. */
2484
	*slash = '\0';
2485
	dirname = real_dir_from_tilde(dirname);
2486
2487
2488
2489
2490
2491
	/* 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);
	}
2492
	free(wasdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2493
    } else {
2494
	filename = dirname;
2495
	dirname = mallocstrcpy(NULL, present_path);
Chris Allegretta's avatar
Chris Allegretta committed
2496
2497
    }

2498
    assert(dirname[strlen(dirname) - 1] == '/');
2499

Chris Allegretta's avatar
Chris Allegretta committed
2500
    dir = opendir(dirname);
2501

2502
    if (dir == NULL) {
2503
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2504
	beep();
2505
2506
2507
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2508
    }
2509
2510
2511

    filenamelen = strlen(filename);

2512
    while ((nextdir = readdir(dir)) != NULL) {
2513
2514
	bool skip_match = FALSE;

Chris Allegretta's avatar
Chris Allegretta committed
2515
#ifdef DEBUG
2516
	fprintf(stderr, "Comparing \'%s\'\n", nextdir->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2517
#endif
2518
	/* See if this matches. */
2519
	if (strncmp(nextdir->d_name, filename, filenamelen) == 0 &&
2520
2521
		(*filename == '.' || (strcmp(nextdir->d_name, ".") != 0 &&
		strcmp(nextdir->d_name, "..") != 0))) {
2522
2523
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2524

2525
	    char *tmp = charalloc(strlen(dirname) + strlen(nextdir->d_name) + 1);
2526
2527
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

2528
2529
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2530
2531
2532
2533
2534
2535
2536
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(tmp, TRUE))
		skip_match = TRUE;
#endif

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

	    free(tmp);
2541

2542
	    if (skip_match)
2543
		continue;
2544

2545
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
2546
					sizeof(char *));
2547
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2548
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2549
2550
	}
    }
2551

2552
2553
    closedir(dir);
    free(dirname);
2554
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2555

2556
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2557
2558
}

2559
/* Do tab completion.  place refers to how much the statusbar cursor
2560
2561
 * position should be advanced.  refresh_func is the function we will
 * call to refresh the edit window. */
2562
char *input_tab(char *buf, bool allow_files, size_t *place,
2563
	bool *lastwastab, void (*refresh_func)(void), bool *listed)
Chris Allegretta's avatar
Chris Allegretta committed
2564
{
2565
    size_t num_matches = 0, buf_len;
2566
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2567

2568
    assert(buf != NULL && place != NULL && *place <= strlen(buf) &&
2569
2570
2571
		lastwastab != NULL && refresh_func != NULL && listed != NULL);

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

2573
2574
    /* 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
2575
    if (*place > 0 && *buf == '~') {
2576
	const char *slash = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2577

2578
	if (slash == NULL || slash >= buf + *place)
2579
	    matches = username_tab_completion(buf, &num_matches, *place);
2580
    }
2581

2582
2583
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
2584
	matches = cwd_tab_completion(buf, allow_files, &num_matches, *place);
2585

2586
2587
2588
    buf_len = strlen(buf);

    if (num_matches == 0 || *place != buf_len)
2589
2590
2591
	beep();
    else {
	size_t match, common_len = 0;
2592
	char *mzero, *glued;
2593
	const char *lastslash = revstrstr(buf, "/", buf + *place);
2594
	size_t lastslash_len = (lastslash == NULL) ? 0 : lastslash - buf + 1;
2595
	char char1[MAXCHARLEN], char2[MAXCHARLEN];
2596
	int len1, len2;
2597

2598
	/* Get the number of characters that all matches have in common. */
2599
	while (TRUE) {
2600
	    len1 = parse_mbchar(matches[0] + common_len, char1, NULL);
2601
2602

	    for (match = 1; match < num_matches; match++) {
2603
2604
2605
		len2 = parse_mbchar(matches[match] + common_len, char2, NULL);

		if (len1 != len2 || strncmp(char1, char2, len2) != 0)
2606
2607
		    break;
	    }
2608

2609
	    if (match < num_matches || matches[0][common_len] == '\0')
2610
		break;
2611

2612
	    common_len += len1;
2613
	}
2614

2615
	mzero = charalloc(lastslash_len + common_len + 1);
2616
2617
2618

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

2620
	common_len += lastslash_len;
2621
	mzero[common_len] = '\0';
2622

2623
2624
2625
2626
	/* 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);

2627
	assert(common_len >= *place);
2628

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

2632
2633
	    assert(common_len > *place);
	}
2634

2635
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2636
	    beep();
2637

2638
	/* If the matches have something in common, show that part. */
2639
	if (common_len != *place) {
2640
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
2641
	    charmove(buf + common_len, buf + *place, buf_len - *place + 1);
2642
	    strncpy(buf, mzero, common_len);
2643
	    *place = common_len;
2644
2645
2646
	}

	if (!*lastwastab)
2647
	    *lastwastab = TRUE;
2648
	else if (num_matches > 1) {
2649
	    int longest_name = 0, ncols, editline = 0;
2650

2651
	    /* Sort the list of available choices. */
2652
2653
	    qsort(matches, num_matches, sizeof(char *), diralphasort);

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

2658
2659
		if (namelen > longest_name)
		    longest_name = namelen;
2660
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2661

2662
2663
	    if (longest_name > COLS - 1)
		longest_name = COLS - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2664

2665
	    /* Each column will be (longest_name + 2) columns wide, i.e.
2666
2667
	     * two spaces between columns, except that there will be
	     * only one space after the last column. */
2668
	    ncols = (COLS + 1) / (longest_name + 2);
2669

2670
	    /* Blank the edit window and hide the cursor. */
2671
2672
	    blank_edit();
	    curs_set(0);
2673
	    wmove(edit, 0, 0);
2674

2675
	    /* Now print the list of matches out there. */
2676
2677
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2678

2679
		wmove(edit, editline, (longest_name + 2) * (match % ncols));
2680

2681
		if (match % ncols == 0 && editline == editwinrows - 1 &&
2682
			num_matches - match > ncols) {
2683
2684
2685
		    waddstr(edit, _("(more)"));
		    break;
		}
2686

2687
		disp = display_string(matches[match], 0, longest_name, FALSE);
2688
2689
		waddstr(edit, disp);
		free(disp);
2690

2691
		if ((match + 1) % ncols == 0)
2692
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2693
	    }
2694

2695
	    wnoutrefresh(edit);
2696
	    *listed = TRUE;
2697
2698
	}

2699
	free(glued);
2700
	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2701
2702
    }

2703
    free_chararray(matches, num_matches);
2704

2705
2706
2707
    /* 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)
2708
2709
	refresh_func();

2710
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2711
}
2712
#endif /* ENABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2713

2714
2715
/* Return the filename part of the given path. */
const char *tail(const char *path)
2716
{
2717
    const char *slash = strrchr(path, '/');
2718

2719
2720
    if (slash == NULL)
	return path;
2721
    else
2722
	return ++slash;
2723
2724
}

2725
2726
#ifndef DISABLE_HISTORIES
/* Return the constructed dirfile path, or NULL if we can't find the home
2727
 * directory.  The string is dynamically allocated, and should be freed. */
2728
char *construct_filename(const char *str)
2729
{
2730
    char *newstr = NULL;
2731

2732
2733
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2734

2735
2736
2737
	newstr = charalloc(homelen + strlen(str) + 1);
	strcpy(newstr, homedir);
	strcpy(newstr + homelen, str);
Chris Allegretta's avatar
Chris Allegretta committed
2738
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2739

2740
2741
2742
2743
2744
    return newstr;
}

char *histfilename(void)
{
2745
    return construct_filename("/.nano/search_history");
2746
2747
}

2748
2749
/* Construct the legacy history filename. */
/* (To be removed in 2018.) */
2750
2751
2752
2753
2754
2755
2756
char *legacyhistfilename(void)
{
    return construct_filename("/.nano_history");
}

char *poshistfilename(void)
{
2757
    return construct_filename("/.nano/filepos_history");
2758
2759
2760
2761
}

void history_error(const char *msg, ...)
{
2762
    va_list ap;
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772

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

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

2773
2774
2775
/* 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. */
2776
2777
int check_dotnano(void)
{
2778
    int ret = 1;
2779
2780
2781
2782
2783
    struct stat dirstat;
    char *nanodir = construct_filename("/.nano");

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

    free(nanodir);
    return ret;
2800
2801
}

2802
/* Load the search and replace histories from ~/.nano/search_history. */
2803
2804
2805
void load_history(void)
{
    char *nanohist = histfilename();
2806
2807
2808
    char *legacyhist = legacyhistfilename();
    struct stat hstat;

2809
2810
    /* If there is an old history file, migrate it. */
    /* (To be removed in 2018.) */
2811
    if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
2812
	if (rename(legacyhist, nanohist) == -1)
2813
2814
2815
	    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));
2816
	else
2817
2818
2819
	    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);
2820
2821
    }

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

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

2842
2843
	    while ((read = getline(&line, &buf_len, hist)) > 0) {
		line[--read] = '\0';
2844
2845
2846
		if (read > 0) {
		    /* Encode any embedded NUL as 0x0A. */
		    unsunder(line, read);
2847
		    update_history(history, line);
2848
		} else
2849
2850
		    history = &replace_history;
	    }
2851

2852
	    fclose(hist);
2853
	    free(line);
2854
	}
2855
	free(nanohist);
2856
	free(legacyhist);
2857
2858
2859
    }
}

2860
/* Write the lines of a history list, starting with the line at head, to
2861
2862
 * the open file at hist.  Return TRUE if the write succeeded, and FALSE
 * otherwise. */
2863
bool writehist(FILE *hist, const filestruct *head)
2864
{
2865
    const filestruct *item;
2866

2867
    /* Write a history list, from the oldest item to the newest. */
2868
2869
    for (item = head; item != NULL; item = item->next) {
	size_t length = strlen(item->data);
2870

2871
2872
2873
	/* Decode 0x0A bytes as embedded NULs. */
	sunder(item->data);

2874
2875
2876
	if (fwrite(item->data, sizeof(char), length, hist) < length)
	    return FALSE;
	if (putc('\n', hist) == EOF)
2877
	    return FALSE;
2878
    }
2879

2880
    return TRUE;
2881
2882
}

2883
/* Save the search and replace histories to ~/.nano/search_history. */
2884
2885
void save_history(void)
{
2886
    char *nanohist;
2887

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2888
    /* Don't save unchanged or empty histories. */
2889
    if (!history_has_changed() || (searchbot->lineno == 1 &&
2890
		replacebot->lineno == 1))
2891
2892
	return;

2893
2894
2895
2896
    nanohist = histfilename();

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

2898
	if (hist == NULL)
2899
2900
	    fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
			strerror(errno));
2901
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2902
2903
	    /* Make sure no one else can read from or write to the
	     * history file. */
2904
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2905

2906
	    if (!writehist(hist, searchage) || !writehist(hist, replaceage))
2907
		fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
2908
			strerror(errno));
2909

2910
2911
	    fclose(hist);
	}
2912

2913
2914
2915
	free(nanohist);
    }
}
2916

2917
/* Save the recorded last file positions to ~/.nano/filepos_history. */
2918
2919
void save_poshistory(void)
{
2920
    char *poshist = poshistfilename();
2921
    poshiststruct *posptr;
2922
    FILE *hist;
2923

2924
2925
    if (poshist == NULL)
	return;
2926

2927
    hist = fopen(poshist, "wb");
2928

2929
2930
2931
2932
2933
    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);
2934

2935
	for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2936
	    char *path_and_place;
2937
2938
	    size_t length;

2939
2940
	    /* Assume 20 decimal positions each for line and column number,
	     * plus two spaces, plus the line feed, plus the null byte. */
2941
2942
	    path_and_place = charalloc(strlen(posptr->filename) + 44);
	    sprintf(path_and_place, "%s %ld %ld\n", posptr->filename,
2943
			(long)posptr->lineno, (long)posptr->xno);
2944
	    length = strlen(path_and_place);
2945
2946

	    /* Encode newlines in filenames as nulls. */
2947
	    sunder(path_and_place);
2948
	    /* Restore the terminating newline. */
2949
	    path_and_place[length - 1] = '\n';
2950

2951
	    if (fwrite(path_and_place, sizeof(char), length, hist) < length)
2952
2953
		fprintf(stderr, _("Error writing %s: %s\n"),
					poshist, strerror(errno));
2954
	    free(path_and_place);
2955
	}
2956
	fclose(hist);
2957
    }
2958
    free(poshist);
2959
2960
}

2961
2962
/* 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. */
2963
2964
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
2965
    poshiststruct *posptr, *theone, *posprev = NULL;
2966
    char *fullpath = get_full_path(filename);
2967

2968
    if (fullpath == NULL || fullpath[strlen(fullpath) - 1] == '/' || inhelp) {
2969
	free(fullpath);
2970
	return;
2971
    }
2972

2973
    /* Look for a matching filename in the list. */
2974
    for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2975
2976
	if (!strcmp(posptr->filename, fullpath))
	    break;
2977
2978
2979
	posprev = posptr;
    }

2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
    /* 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;
    }

2994
    theone = posptr;
2995

2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
    /* 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) {
3006
3007
3008
3009
	if (posprev == NULL)
	    position_history = posptr->next;
	else
	    posprev->next = posptr->next;
3010
3011
3012
3013
3014
3015
3016
3017
3018
	while (posptr->next != NULL)
	    posptr = posptr->next;
	posptr->next = theone;
    }

    /* Store the last cursor position. */
    theone->lineno = lineno;
    theone->xno = xpos;
    theone->next = NULL;
3019
3020
3021
3022

    free(fullpath);
}

3023
3024
3025
3026
/* 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)
3027
{
3028
    poshiststruct *posptr = position_history;
3029
3030
3031
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
3032
3033
3034
3035
	return FALSE;

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

3037
    free(fullpath);
3038
3039

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

    *line = posptr->lineno;
    *column = posptr->xno;
    return TRUE;
3045
3046
}

3047
/* Load the recorded file positions from ~/.nano/filepos_history. */
3048
3049
void load_poshistory(void)
{
3050
    char *poshist = poshistfilename();
3051
    FILE *hist;
3052

3053
3054
3055
    /* If the home directory is missing, do_rcfile() will have reported it. */
    if (poshist == NULL)
	return;
3056

3057
    hist = fopen(poshist, "rb");
3058

3059
3060
    if (hist == NULL) {
	if (errno != ENOENT) {
3061
	    /* When reading failed, don't save history when we quit. */
3062
3063
	    UNSET(POS_HISTORY);
	    history_error(N_("Error reading %s: %s"), poshist, strerror(errno));
3064
	}
3065
3066
3067
    } else {
	char *line = NULL, *lineptr, *xptr;
	size_t buf_len = 0;
3068
	ssize_t read, count = 0;
3069
3070
3071
	poshiststruct *record_ptr = NULL, *newrecord;

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

3076
3077
	    /* Find where the x index and line number are in the line. */
	    xptr = revstrstr(line, " ", line + read - 3);
3078
3079
	    if (xptr == NULL)
		continue;
3080
	    lineptr = revstrstr(line, " ", xptr - 2);
3081
3082
	    if (lineptr == NULL)
		continue;
3083
3084
3085
3086

	    /* Now separate the three elements of the line. */
	    *(xptr++) = '\0';
	    *(lineptr++) = '\0';
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101

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

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

3107
		position_history = position_history->next;
3108
3109
3110
3111

		free(drop_record->filename);
		free(drop_record);
	    }
3112
3113
3114
	}
	fclose(hist);
	free(line);
3115
    }
3116
    free(poshist);
3117
}
3118
#endif /* !DISABLE_HISTORIES */