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

22
#include "proto.h"
23

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

33
34
#define LOCKBUFSIZE 8192

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

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

57
    free(namecopy);
58

59
    return validity;
60
61
}

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

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

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

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

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

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

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

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

99
    openfile->fmt = NIX_FILE;
100

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

336
337
	close(lockfd);

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

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

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

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

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

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

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

    retval = write_lockfile(lockfilename, filename, FALSE);

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

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

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

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

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

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

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

441
442
    realname = real_dir_from_tilde(filename);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    if (descriptor < 0)
	return;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    if (ISSET(VIEW_MODE))
	return TRUE;

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

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

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

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

    return result;
717
718
}

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

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

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

765
    buf = charalloc(bufx);
766

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

889
    free(buf);
890

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

996
997
    free(full_filename);

998
999
1000
    return fd;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1188
		refresh_needed = TRUE;
1189
	    }
1190

1191
1192
1193
	    break;
	}
    }
1194
1195

    free(given);
1196
1197
}

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

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

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

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

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

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

1255
    d_there = real_dir_from_tilde(origpath);
1256

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1340
    return d_there;
1341
}
1342

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

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

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

    return full_path;
}

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

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

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

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

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

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

    fd = mkstemp(full_tempdir);

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

1405
1406
    umask(original_umask);

1407
    return full_tempdir;
1408
}
1409
1410

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

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

1420
    snuggly_fit(&operating_dir);
1421
1422
}

1423
1424
1425
1426
/* Check 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. */
1427
bool outside_of_confinement(const char *currpath, bool allow_tabcomp)
1428
{
1429
    char *fullpath;
1430
    bool retval = FALSE;
1431
    const char *whereami1, *whereami2 = NULL;
1432

1433
    /* If no operating directory is set, don't bother doing anything. */
1434
    if (operating_dir == NULL)
1435
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1436

1437
    fullpath = get_full_path(currpath);
1438

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

1449
    whereami1 = strstr(fullpath, operating_dir);
1450
    if (allow_tabcomp)
1451
	whereami2 = strstr(operating_dir, fullpath);
1452

1453
    /* If both searches failed, we're outside the operating directory.
1454
     * Otherwise, check the search results.  If the full operating
1455
1456
1457
     * 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. */
1458
    if (whereami1 != fullpath && whereami2 != operating_dir)
1459
1460
	retval = TRUE;
    free(fullpath);
1461
1462

    /* Otherwise, we're still inside it. */
1463
    return retval;
1464
}
1465
1466
#endif

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

    return response;
1483
1484
}

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

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

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

1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
    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
1524

1525
1526
    if (fclose(inn) == EOF)
	retval = -1;
1527
    if (flush_out_fnc(out) == EOF)
1528
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1529

1530
1531
1532
    return retval;
}

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1575
    if (*name == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1576
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1577

1578
1579
    if (!tmp)
	titlebar(NULL);
1580

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1581
    realname = real_dir_from_tilde(name);
1582

1583
#ifndef DISABLE_OPERATINGDIR
1584
    /* If we're writing a temporary file, we're probably going outside
1585
     * the operating directory, so skip the operating directory test. */
1586
    if (!tmp && outside_of_confinement(realname, FALSE)) {
1587
	statusline(ALERT, _("Can't write outside of %s"), operating_dir);
1588
	goto cleanup_and_exit;
1589
1590
1591
    }
#endif

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

1596
#ifndef NANO_TINY
1597
    /* Check whether the file (at the end of the symlink) exists. */
1598
    realexists = (stat(realname, &st) != -1);
1599

1600
1601
1602
1603
    /* 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. */
1604
1605
    if (openfile->current_stat == NULL && !tmp && realexists)
	stat_with_alloc(realname, &openfile->current_stat);
1606

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

1621
	/* Save the original file's access and modification times. */
1622
1623
	filetime[0].tv_sec = openfile->current_stat->st_atime;
	filetime[1].tv_sec = openfile->current_stat->st_mtime;
1624

1625
1626
1627
	if (f_open == NULL) {
	    /* Open the original file to copy to the backup. */
	    f = fopen(realname, "rb");
1628

1629
	    if (f == NULL) {
1630
		statusline(ALERT, _("Error reading %s: %s"), realname,
1631
			strerror(errno));
1632
1633
1634
1635
		/* 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;
1636
	    }
1637
1638
	}

1639
	/* If backup_dir is set, we set backupname to
1640
1641
1642
1643
	 * 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]. */
1644
	if (backup_dir != NULL) {
1645
	    char *backuptemp = get_full_path(realname);
1646

1647
	    if (backuptemp == NULL)
1648
1649
1650
1651
1652
1653
		/* 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~. */
1654
		backuptemp = mallocstrcpy(NULL, tail(realname));
1655
	    else {
1656
1657
		size_t i = 0;

1658
1659
1660
		for (; backuptemp[i] != '\0'; i++) {
		    if (backuptemp[i] == '/')
			backuptemp[i] = '!';
1661
1662
1663
		}
	    }

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

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

1700
1701
	if (ISSET(INSECURE_BACKUP))
	    backup_cflags = O_WRONLY | O_CREAT | O_APPEND;
1702
	else
1703
1704
1705
	    backup_cflags = O_WRONLY | O_CREAT | O_EXCL | O_APPEND;

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

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

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

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

#ifdef DEBUG
1749
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1750
1751
#endif

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

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

1770
	fclose(backup_file);
1771
1772
	free(backupname);
    }
1773

1774
    skip_backup:
1775
#endif /* !NANO_TINY */
1776

1777
1778
    if (f_open == NULL) {
	original_umask = umask(0);
1779

1780
	/* If we create a temp file, we don't let anyone else access it.
1781
1782
	 * We create a temp file if tmp is TRUE. */
	if (tmp)
1783
	    umask(S_IRWXG | S_IRWXO);
1784
1785
	else
	    umask(original_umask);
1786
    }
1787

1788
    /* If we're prepending, copy the file to a temp file. */
1789
    if (method == PREPEND) {
1790
1791
1792
	int fd_source;
	FILE *f_source = NULL;

1793
1794
1795
1796
	if (f == NULL) {
	    f = fopen(realname, "rb");

	    if (f == NULL) {
1797
		statusline(ALERT, _("Error reading %s: %s"), realname,
1798
1799
1800
1801
1802
			strerror(errno));
		goto cleanup_and_exit;
	    }
	}

1803
	tempname = safe_tempfile(&f);
1804

1805
	if (tempname == NULL) {
1806
	    statusline(ALERT, _("Error writing temp file: %s"),
1807
			strerror(errno));
1808
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1809
	}
1810

1811
	if (f_open == NULL) {
1812
	    fd_source = open(realname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1813

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

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

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

1842
1843
	/* Set the umask back to the user's original value. */
	umask(original_umask);
1844

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

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

1856
	if (f == NULL) {
1857
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1858
			strerror(errno));
1859
1860
1861
	    close(fd);
	    goto cleanup_and_exit;
	}
1862
1863
    }

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

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

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

1872
1873
	/* Convert nulls to newlines.  data_len is the string's real
	 * length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1874
1875
	unsunder(fileptr->data, data_len);

1876
	if (size < data_len) {
1877
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1878
			strerror(errno));
1879
	    fclose(f);
1880
	    goto cleanup_and_exit;
1881
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1882

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

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

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

1915
    /* If we're prepending, open the temp file, and append it to f. */
1916
    if (method == PREPEND) {
1917
1918
1919
	int fd_source;
	FILE *f_source = NULL;

1920
	fd_source = open(tempname, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
1921

1922
1923
1924
1925
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1926
	}
1927

1928
	if (f_source == NULL) {
1929
1930
	    statusline(ALERT, _("Error reading %s: %s"), tempname,
			strerror(errno));
1931
	    fclose(f);
1932
	    goto cleanup_and_exit;
1933
1934
	}

1935
	if (copy_file(f_source, f, TRUE) != 0) {
1936
	    statusline(ALERT, _("Error writing %s: %s"), realname,
1937
			strerror(errno));
1938
	    goto cleanup_and_exit;
1939
	}
1940
1941

	unlink(tempname);
1942
    } else if (fclose(f) != 0) {
1943
	statusline(ALERT, _("Error writing %s: %s"), realname,
1944
			strerror(errno));
1945
	goto cleanup_and_exit;
1946
    }
1947

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

1958
1959
#ifndef DISABLE_COLOR
	    /* See if the applicable syntax has changed. */
1960
	    color_update();
1961
	    color_init();
1962

1963
	    newname = openfile->syntax ? openfile->syntax->name : "";
1964

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

1977
#ifndef NANO_TINY
1978
	if (!openfile->mark_set)
1979
1980
	    /* Get or update the stat info to reflect the current state. */
	    stat_with_alloc(realname, &openfile->current_stat);
1981
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1982

1983
1984
	statusline(HUSH, P_("Wrote %lu line", "Wrote %lu lines",
		(unsigned long)lineswritten), (unsigned long)lineswritten);
1985
	openfile->modified = FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
1986
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1987
    }
1988

1989
    retval = TRUE;
1990
1991
1992

  cleanup_and_exit:
    free(realname);
1993
    free(tempname);
1994

1995
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1996
1997
}

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

2012
    /* Partition the buffer so that it contains only the marked text. */
2013
    mark_order((const filestruct **)&top, &top_x,
2014
		(const filestruct **)&bot, &bot_x, NULL);
2015
    filepart = partition_filestruct(top, top_x, bot, bot_x);
2016

2017
2018
2019
    /* 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') {
2020
	new_magicline();
2021
2022
	added_magicline = TRUE;
    }
2023

2024
    retval = write_file(name, f_open, tmp, method, TRUE);
2025

2026
2027
    /* If we added a magicline, remove it now. */
    if (added_magicline)
2028
2029
	remove_magicline();

2030
    /* Unpartition the buffer so that it contains all the text again. */
2031
    unpartition_filestruct(&filepart);
2032

2033
    if (old_modified)
2034
2035
2036
2037
	set_modified();

    return retval;
}
2038

2039
#endif /* !NANO_TINY */
2040

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

2059
    /* Display newlines in filenames as ^J. */
2060
2061
    as_an_at = FALSE;

2062
2063
2064
2065
    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
2066
2067
    }

2068
    given = mallocstrcpy(NULL,
2069
#ifndef NANO_TINY
2070
	(openfile->mark_set && !exiting) ? "" :
2071
#endif
2072
	openfile->filename);
2073
2074
2075

    while (TRUE) {
	const char *msg;
2076
#ifndef NANO_TINY
2077
2078
	const char *formatstr, *backupstr;

2079
	formatstr = (openfile->fmt == DOS_FILE) ? _(" [DOS Format]") :
2080
			(openfile->fmt == MAC_FILE) ? _(" [Mac Format]") : "";
2081

2082
	backupstr = ISSET(BACKUP_FILE) ? _(" [Backup]") : "";
2083

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

2100
2101
	present_path = mallocstrcpy(present_path, "./");

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

2117
	if (i < 0) {
2118
	    statusbar(_("Cancelled"));
2119
2120
	    break;
	} else {
2121
2122
	    functionptrtype func = func_from_key(&i);

2123
	    /* Upon request, abandon the buffer. */
2124
	    if (func == discard_buffer) {
2125
2126
		free(given);
		return 2;
2127
2128
	    }

2129
	    given = mallocstrcpy(given, answer);
Chris Allegretta's avatar
Chris Allegretta committed
2130

2131
#ifdef ENABLE_BROWSER
2132
	    if (func == to_files_void) {
2133
		char *chosen = do_browse_from(answer);
2134

2135
		if (chosen == NULL)
2136
		    continue;
2137

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

Chris Allegretta's avatar
Chris Allegretta committed
2167
#ifdef DEBUG
2168
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
2169
#endif
2170

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

2186
	    if (method == OVERWRITE) {
2187
		bool name_exists, do_warning;
2188
		char *full_answer, *full_filename;
2189
2190
		struct stat st;

2191
2192
		full_answer = get_full_path(answer);
		full_filename = get_full_path(openfile->filename);
2193
2194
		name_exists = (stat((full_answer == NULL) ?
				answer : full_answer, &st) != -1);
2195
2196
2197
2198
		if (openfile->filename[0] == '\0')
		    do_warning = name_exists;
		else
		    do_warning = (strcmp((full_answer == NULL) ?
2199
2200
				answer : full_answer, (full_filename == NULL) ?
				openfile->filename : full_filename) != 0);
2201

2202
2203
		free(full_filename);
		free(full_answer);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2204

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

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

2250
2251
		    warn_and_shortly_pause(_("File on disk has changed"));

2252
2253
		    if (do_yesno_prompt(FALSE, _("File was modified since "
				"you opened it; continue saving? ")) < 1)
2254
2255
			continue;
		}
2256
#endif
2257
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2258

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

2270
2271
	    break;
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2272
    }
2273

2274
    free(given);
2275

2276
    return result ? 1 : 0;
Chris Allegretta's avatar
Chris Allegretta committed
2277
2278
}

2279
/* Write the current buffer to disk, or discard it. */
2280
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
2281
{
2282
2283
2284
    /* 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
2285
}
Chris Allegretta's avatar
Chris Allegretta committed
2286

2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
#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

2298
2299
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
2300
char *real_dir_from_tilde(const char *buf)
2301
{
2302
    char *retval;
2303

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2304
    if (*buf == '~') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2305
	size_t i = 1;
2306
	char *tilde_dir;
2307

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

2312
2313
2314
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
2315
	    tilde_dir = mallocstrcpy(NULL, homedir);
2316
	} else {
2317
#ifdef HAVE_PWD_H
2318
2319
	    const struct passwd *userdata;

2320
2321
2322
	    tilde_dir = mallocstrncpy(NULL, buf, i + 1);
	    tilde_dir[i] = '\0';

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

2335
2336
	retval = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	sprintf(retval, "%s%s", tilde_dir, buf + i);
2337

2338
2339
2340
	free(tilde_dir);
    } else
	retval = mallocstrcpy(NULL, buf);
2341

2342
    return retval;
2343
2344
}

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

2361
2362
2363
    /* 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
2364
     * have to use multibyte strcasecmp() instead. */
2365
    return mbstrcasecmp(a, b);
2366
}
2367
2368
2369
2370
2371

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

2375
2376
    for (; len > 0; len--)
	free(array[len - 1]);
2377

2378
2379
    free(array);
}
2380
2381
#endif

2382
#ifdef ENABLE_TABCOMP
2383
/* Is the given path a directory? */
2384
bool is_dir(const char *buf)
2385
{
2386
    char *dirptr;
2387
    struct stat fileinfo;
2388
2389
2390
2391
    bool retval;

    dirptr = real_dir_from_tilde(buf);

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

2394
    free(dirptr);
2395

2396
    return retval;
2397
}
Chris Allegretta's avatar
Chris Allegretta committed
2398

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

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

2427
    assert(buf != NULL && num_matches != NULL && buf_len > 0);
2428

2429
    *num_matches = 0;
2430

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

2437
2438
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2439
	     * directory, in which case just go to the next match. */
2440
	    if (outside_of_confinement(userdata->pw_dir, TRUE))
2441
		continue;
2442
2443
#endif

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

2454
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2455
2456
}

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

2469
    *num_matches = 0;
2470
    dirname[buf_len] = '\0';
2471

2472
    /* If there's a / in the name, split out filename and directory parts. */
2473
2474
2475
    slash = strrchr(dirname, '/');
    if (slash != NULL) {
	char *wasdirname = dirname;
2476

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

2493
    assert(dirname[strlen(dirname) - 1] == '/');
2494

Chris Allegretta's avatar
Chris Allegretta committed
2495
    dir = opendir(dirname);
2496

2497
    if (dir == NULL) {
2498
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2499
	beep();
2500
2501
2502
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2503
    }
2504
2505
2506

    filenamelen = strlen(filename);

2507
    while ((nextdir = readdir(dir)) != NULL) {
2508
2509
	bool skip_match = FALSE;

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

2520
	    char *tmp = charalloc(strlen(dirname) + strlen(nextdir->d_name) + 1);
2521
2522
	    sprintf(tmp, "%s%s", dirname, nextdir->d_name);

2523
2524
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2525
	     * directory, in which case just go to the next match. */
2526
	    skip_match = outside_of_confinement(tmp, TRUE);
2527
2528
2529
2530
#endif

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

	    free(tmp);
2534

2535
	    if (skip_match)
2536
		continue;
2537

2538
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
2539
					sizeof(char *));
2540
	    matches[*num_matches] = mallocstrcpy(NULL, nextdir->d_name);
2541
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2542
2543
	}
    }
2544

2545
2546
    closedir(dir);
    free(dirname);
2547
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2548

2549
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2550
2551
}

2552
/* Do tab completion.  place refers to how much the statusbar cursor
2553
2554
 * position should be advanced.  refresh_func is the function we will
 * call to refresh the edit window. */
2555
char *input_tab(char *buf, bool allow_files, size_t *place,
2556
	bool *lastwastab, void (*refresh_func)(void), bool *listed)
Chris Allegretta's avatar
Chris Allegretta committed
2557
{
2558
    size_t num_matches = 0, buf_len;
2559
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2560

2561
    assert(buf != NULL && place != NULL && *place <= strlen(buf) &&
2562
2563
2564
		lastwastab != NULL && refresh_func != NULL && listed != NULL);

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

2566
2567
    /* 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
2568
    if (*place > 0 && *buf == '~') {
2569
	const char *slash = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2570

2571
	if (slash == NULL || slash >= buf + *place)
2572
	    matches = username_tab_completion(buf, &num_matches, *place);
2573
    }
2574

2575
2576
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
2577
	matches = cwd_tab_completion(buf, allow_files, &num_matches, *place);
2578

2579
2580
2581
    buf_len = strlen(buf);

    if (num_matches == 0 || *place != buf_len)
2582
2583
2584
	beep();
    else {
	size_t match, common_len = 0;
2585
	char *mzero, *glued;
2586
	const char *lastslash = revstrstr(buf, "/", buf + *place);
2587
	size_t lastslash_len = (lastslash == NULL) ? 0 : lastslash - buf + 1;
2588
	char char1[MAXCHARLEN], char2[MAXCHARLEN];
2589
	int len1, len2;
2590

2591
	/* Get the number of characters that all matches have in common. */
2592
	while (TRUE) {
2593
	    len1 = parse_mbchar(matches[0] + common_len, char1, NULL);
2594
2595

	    for (match = 1; match < num_matches; match++) {
2596
2597
2598
		len2 = parse_mbchar(matches[match] + common_len, char2, NULL);

		if (len1 != len2 || strncmp(char1, char2, len2) != 0)
2599
2600
		    break;
	    }
2601

2602
	    if (match < num_matches || matches[0][common_len] == '\0')
2603
		break;
2604

2605
	    common_len += len1;
2606
	}
2607

2608
	mzero = charalloc(lastslash_len + common_len + 1);
2609
2610
2611

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

2613
	common_len += lastslash_len;
2614
	mzero[common_len] = '\0';
2615

2616
2617
2618
2619
	/* 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);

2620
	assert(common_len >= *place);
2621

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

2625
2626
	    assert(common_len > *place);
	}
2627

2628
	if (num_matches > 1 && (common_len != *place || !*lastwastab))
2629
	    beep();
2630

2631
	/* If the matches have something in common, show that part. */
2632
	if (common_len != *place) {
2633
	    buf = charealloc(buf, common_len + buf_len - *place + 1);
2634
	    charmove(buf + common_len, buf + *place, buf_len - *place + 1);
2635
	    strncpy(buf, mzero, common_len);
2636
	    *place = common_len;
2637
2638
2639
	}

	if (!*lastwastab)
2640
	    *lastwastab = TRUE;
2641
	else if (num_matches > 1) {
2642
	    int longest_name = 0, ncols, editline = 0;
2643

2644
	    /* Sort the list of available choices. */
2645
2646
	    qsort(matches, num_matches, sizeof(char *), diralphasort);

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

2651
2652
		if (namelen > longest_name)
		    longest_name = namelen;
2653
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2654

2655
2656
	    if (longest_name > COLS - 1)
		longest_name = COLS - 1;
Chris Allegretta's avatar
Chris Allegretta committed
2657

2658
	    /* Each column will be (longest_name + 2) columns wide, i.e.
2659
2660
	     * two spaces between columns, except that there will be
	     * only one space after the last column. */
2661
	    ncols = (COLS + 1) / (longest_name + 2);
2662

2663
	    /* Blank the edit window and hide the cursor. */
2664
2665
	    blank_edit();
	    curs_set(0);
2666
	    wmove(edit, 0, 0);
2667

2668
	    /* Now print the list of matches out there. */
2669
2670
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2671

2672
		wmove(edit, editline, (longest_name + 2) * (match % ncols));
2673

2674
		if (match % ncols == 0 && editline == editwinrows - 1 &&
2675
			num_matches - match > ncols) {
2676
2677
2678
		    waddstr(edit, _("(more)"));
		    break;
		}
2679

2680
		disp = display_string(matches[match], 0, longest_name, FALSE);
2681
2682
		waddstr(edit, disp);
		free(disp);
2683

2684
		if ((match + 1) % ncols == 0)
2685
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2686
	    }
2687

2688
	    wnoutrefresh(edit);
2689
	    *listed = TRUE;
2690
2691
	}

2692
	free(glued);
2693
	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2694
2695
    }

2696
    free_chararray(matches, num_matches);
2697

2698
2699
2700
    /* 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)
2701
2702
	refresh_func();

2703
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2704
}
2705
#endif /* ENABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2706

2707
2708
/* Return the filename part of the given path. */
const char *tail(const char *path)
2709
{
2710
    const char *slash = strrchr(path, '/');
2711

2712
2713
    if (slash == NULL)
	return path;
2714
    else
2715
	return ++slash;
2716
2717
}

2718
2719
#ifndef DISABLE_HISTORIES
/* Return the constructed dirfile path, or NULL if we can't find the home
2720
 * directory.  The string is dynamically allocated, and should be freed. */
2721
char *construct_filename(const char *str)
2722
{
2723
    char *newstr = NULL;
2724

2725
2726
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2727

2728
2729
2730
	newstr = charalloc(homelen + strlen(str) + 1);
	strcpy(newstr, homedir);
	strcpy(newstr + homelen, str);
Chris Allegretta's avatar
Chris Allegretta committed
2731
    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2732

2733
2734
2735
2736
2737
    return newstr;
}

char *histfilename(void)
{
2738
    return construct_filename("/.nano/search_history");
2739
2740
}

2741
2742
/* Construct the legacy history filename. */
/* (To be removed in 2018.) */
2743
2744
2745
2746
2747
2748
2749
char *legacyhistfilename(void)
{
    return construct_filename("/.nano_history");
}

char *poshistfilename(void)
{
2750
    return construct_filename("/.nano/filepos_history");
2751
2752
2753
2754
}

void history_error(const char *msg, ...)
{
2755
    va_list ap;
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765

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

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

2766
2767
2768
/* 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. */
2769
2770
int check_dotnano(void)
{
2771
    int ret = 1;
2772
2773
2774
2775
2776
    struct stat dirstat;
    char *nanodir = construct_filename("/.nano");

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

    free(nanodir);
    return ret;
2793
2794
}

2795
/* Load the search and replace histories from ~/.nano/search_history. */
2796
2797
2798
void load_history(void)
{
    char *nanohist = histfilename();
2799
2800
2801
    char *legacyhist = legacyhistfilename();
    struct stat hstat;

2802
2803
    /* If there is an old history file, migrate it. */
    /* (To be removed in 2018.) */
2804
    if (stat(legacyhist, &hstat) != -1 && stat(nanohist, &hstat) == -1) {
2805
	if (rename(legacyhist, nanohist) == -1)
2806
2807
2808
	    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));
2809
	else
2810
2811
2812
	    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);
2813
2814
    }

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

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

2835
2836
	    while ((read = getline(&line, &buf_len, hist)) > 0) {
		line[--read] = '\0';
2837
2838
2839
		if (read > 0) {
		    /* Encode any embedded NUL as 0x0A. */
		    unsunder(line, read);
2840
		    update_history(history, line);
2841
		} else
2842
2843
		    history = &replace_history;
	    }
2844

2845
	    fclose(hist);
2846
	    free(line);
2847
	}
2848
	free(nanohist);
2849
	free(legacyhist);
2850
2851
2852
    }
}

2853
/* Write the lines of a history list, starting with the line at head, to
2854
2855
 * the open file at hist.  Return TRUE if the write succeeded, and FALSE
 * otherwise. */
2856
bool writehist(FILE *hist, const filestruct *head)
2857
{
2858
    const filestruct *item;
2859

2860
    /* Write a history list, from the oldest item to the newest. */
2861
2862
    for (item = head; item != NULL; item = item->next) {
	size_t length = strlen(item->data);
2863

2864
2865
2866
	/* Decode 0x0A bytes as embedded NULs. */
	sunder(item->data);

2867
2868
2869
	if (fwrite(item->data, sizeof(char), length, hist) < length)
	    return FALSE;
	if (putc('\n', hist) == EOF)
2870
	    return FALSE;
2871
    }
2872

2873
    return TRUE;
2874
2875
}

2876
/* Save the search and replace histories to ~/.nano/search_history. */
2877
2878
void save_history(void)
{
2879
    char *nanohist;
2880

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2881
    /* Don't save unchanged or empty histories. */
2882
    if (!history_has_changed() || (searchbot->lineno == 1 &&
2883
		replacebot->lineno == 1))
2884
2885
	return;

2886
2887
2888
2889
    nanohist = histfilename();

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

2891
	if (hist == NULL)
2892
2893
	    fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
			strerror(errno));
2894
	else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2895
2896
	    /* Make sure no one else can read from or write to the
	     * history file. */
2897
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2898

2899
	    if (!writehist(hist, searchage) || !writehist(hist, replaceage))
2900
		fprintf(stderr, _("Error writing %s: %s\n"), nanohist,
2901
			strerror(errno));
2902

2903
2904
	    fclose(hist);
	}
2905

2906
2907
2908
	free(nanohist);
    }
}
2909

2910
/* Save the recorded last file positions to ~/.nano/filepos_history. */
2911
2912
void save_poshistory(void)
{
2913
    char *poshist = poshistfilename();
2914
    poshiststruct *posptr;
2915
    FILE *hist;
2916

2917
2918
    if (poshist == NULL)
	return;
2919

2920
    hist = fopen(poshist, "wb");
2921

2922
2923
2924
2925
2926
    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);
2927

2928
	for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2929
	    char *path_and_place;
2930
2931
	    size_t length;

2932
2933
	    /* Assume 20 decimal positions each for line and column number,
	     * plus two spaces, plus the line feed, plus the null byte. */
2934
2935
	    path_and_place = charalloc(strlen(posptr->filename) + 44);
	    sprintf(path_and_place, "%s %ld %ld\n", posptr->filename,
2936
			(long)posptr->lineno, (long)posptr->xno);
2937
	    length = strlen(path_and_place);
2938
2939

	    /* Encode newlines in filenames as nulls. */
2940
	    sunder(path_and_place);
2941
	    /* Restore the terminating newline. */
2942
	    path_and_place[length - 1] = '\n';
2943

2944
	    if (fwrite(path_and_place, sizeof(char), length, hist) < length)
2945
2946
		fprintf(stderr, _("Error writing %s: %s\n"),
					poshist, strerror(errno));
2947
	    free(path_and_place);
2948
	}
2949
	fclose(hist);
2950
    }
2951
    free(poshist);
2952
2953
}

2954
2955
/* 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. */
2956
2957
void update_poshistory(char *filename, ssize_t lineno, ssize_t xpos)
{
2958
    poshiststruct *posptr, *theone, *posprev = NULL;
2959
    char *fullpath = get_full_path(filename);
2960

2961
    if (fullpath == NULL || fullpath[strlen(fullpath) - 1] == '/' || inhelp) {
2962
	free(fullpath);
2963
	return;
2964
    }
2965

2966
    /* Look for a matching filename in the list. */
2967
    for (posptr = position_history; posptr != NULL; posptr = posptr->next) {
2968
2969
	if (!strcmp(posptr->filename, fullpath))
	    break;
2970
2971
2972
	posprev = posptr;
    }

2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
    /* 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;
    }

2987
    theone = posptr;
2988

2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
    /* 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) {
2999
3000
3001
3002
	if (posprev == NULL)
	    position_history = posptr->next;
	else
	    posprev->next = posptr->next;
3003
3004
3005
3006
3007
3008
3009
3010
3011
	while (posptr->next != NULL)
	    posptr = posptr->next;
	posptr->next = theone;
    }

    /* Store the last cursor position. */
    theone->lineno = lineno;
    theone->xno = xpos;
    theone->next = NULL;
3012
3013
3014
3015

    free(fullpath);
}

3016
3017
3018
3019
/* 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)
3020
{
3021
    poshiststruct *posptr = position_history;
3022
3023
3024
    char *fullpath = get_full_path(file);

    if (fullpath == NULL)
3025
3026
3027
3028
	return FALSE;

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

3030
    free(fullpath);
3031
3032

    if (posptr == NULL)
3033
	return FALSE;
3034
3035
3036
3037

    *line = posptr->lineno;
    *column = posptr->xno;
    return TRUE;
3038
3039
}

3040
/* Load the recorded file positions from ~/.nano/filepos_history. */
3041
3042
void load_poshistory(void)
{
3043
    char *poshist = poshistfilename();
3044
    FILE *hist;
3045

3046
3047
3048
    /* If the home directory is missing, do_rcfile() will have reported it. */
    if (poshist == NULL)
	return;
3049

3050
    hist = fopen(poshist, "rb");
3051

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

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

3069
3070
	    /* Find where the x index and line number are in the line. */
	    xptr = revstrstr(line, " ", line + read - 3);
3071
3072
	    if (xptr == NULL)
		continue;
3073
	    lineptr = revstrstr(line, " ", xptr - 2);
3074
3075
	    if (lineptr == NULL)
		continue;
3076
3077
3078
3079

	    /* Now separate the three elements of the line. */
	    *(xptr++) = '\0';
	    *(lineptr++) = '\0';
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094

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

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

3100
		position_history = position_history->next;
3101
3102
3103
3104

		free(drop_record->filename);
		free(drop_record);
	    }
3105
3106
3107
	}
	fclose(hist);
	free(line);
3108
    }
3109
    free(poshist);
3110
}
3111
#endif /* !DISABLE_HISTORIES */