files.c 79 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   files.c                                                              *
 *                                                                        *
5
 *   Copyright (C) 1999-2004 Chris Allegretta                             *
Chris Allegretta's avatar
Chris Allegretta committed
6
7
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
8
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
9
10
11
12
13
14
15
16
17
18
19
20
21
 *   any later version.                                                   *
 *                                                                        *
 *   This program 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.                         *
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            *
 *                                                                        *
 **************************************************************************/

22
23
#include "config.h"

Chris Allegretta's avatar
Chris Allegretta committed
24
25
26
27
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
Chris Allegretta's avatar
Chris Allegretta committed
28
#include <sys/wait.h>
Chris Allegretta's avatar
Chris Allegretta committed
29
#include <sys/stat.h>
30
#include <utime.h>
Chris Allegretta's avatar
Chris Allegretta committed
31
32
#include <fcntl.h>
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
33
34
#include <ctype.h>
#include <dirent.h>
35
#include <pwd.h>
Chris Allegretta's avatar
Chris Allegretta committed
36
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
37
38
#include "proto.h"
#include "nano.h"
Chris Allegretta's avatar
Chris Allegretta committed
39

40
41
/* Set a default value for PATH_MAX, so we can use it below in lines
 * like "path = getcwd(NULL, PATH_MAX + 1);". */
42
43
44
45
#ifndef PATH_MAX
#define PATH_MAX -1
#endif

Chris Allegretta's avatar
Chris Allegretta committed
46
47
48
/* What happens when there is no file to open? aiee! */
void new_file(void)
{
49
    fileage = make_new_node(NULL);
50
    fileage->data = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
51
    fileage->data[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
52
53
54
    filebot = fileage;
    edittop = fileage;
    current = fileage;
55
    current_x = 0;
Chris Allegretta's avatar
Chris Allegretta committed
56
    totlines = 1;
57
    totsize = 0;
58

59
60
#ifdef ENABLE_COLOR
    update_color();
61
62
    if (ISSET(COLOR_SYNTAX))
	edit_refresh();
63
#endif
Chris Allegretta's avatar
Chris Allegretta committed
64
65
}

66
67
68
69
70
71
/* We make a new line of text from buf.  buf is length len.  If
 * first_line_ins is TRUE, then we put the new line at the top of the
 * file.  Otherwise, we assume prev is the last line of the file, and
 * put our line after prev. */
filestruct *read_line(char *buf, filestruct *prev, bool *first_line_ins,
	size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
72
{
73
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
74

75
76
    /* Convert nulls to newlines.  len is the string's real length
     * here. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
77
78
    unsunder(buf, len);

79
80
81
    assert(strlen(buf) == len);

    fileptr->data = mallocstrcpy(NULL, buf);
Chris Allegretta's avatar
Chris Allegretta committed
82

83
#ifndef NANO_SMALL
84
85
    /* If it's a DOS file (CR LF), and file conversion isn't disabled,
     * strip out the CR part. */
86
87
    if (!ISSET(NO_CONVERT) && len > 0 && buf[len - 1] == '\r') {
	fileptr->data[len - 1] = '\0';
88
	totsize--;
89
90
91
    }
#endif

92
93
94
    if (*first_line_ins || fileage == NULL) {
	/* Special case: we're inserting with the cursor on the first
	 * line. */
Chris Allegretta's avatar
Chris Allegretta committed
95
96
97
	fileptr->prev = NULL;
	fileptr->next = fileage;
	fileptr->lineno = 1;
98
99
	if (*first_line_ins) {
	    *first_line_ins = FALSE;
100
	    /* If we're inserting into the first line of the file, then
101
102
	     * we want to make sure that our edit buffer stays on the
	     * first line and that fileage stays up to date. */
103
104
105
	    edittop = fileptr;
	} else
	    filebot = fileptr;
Chris Allegretta's avatar
Chris Allegretta committed
106
	fileage = fileptr;
107
108
    } else {
	assert(prev != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
109
110
111
112
113
114
115
116
117
	fileptr->prev = prev;
	fileptr->next = NULL;
	fileptr->lineno = prev->lineno + 1;
	prev->next = fileptr;
    }

    return fileptr;
}

118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/* Load a file into the edit buffer.  This takes data from the file
 * struct. */
void load_file(void)
{
    current = fileage;

#ifdef ENABLE_MULTIBUFFER
    /* Add a new entry to the open_files structure. */
    add_open_file(FALSE);

    /* Reinitialize the shortcut list. */
    shortcut_init(FALSE);
#endif
}

void read_file(FILE *f, const char *filename)
Chris Allegretta's avatar
Chris Allegretta committed
134
{
135
136
137
138
139
140
141
142
143
144
    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. */
    size_t i = 0;
	/* The position in the current line of the file. */
    size_t bufx = 128;
	/* The size of each chunk of the file that we read. */
    char input = '\0';
	/* The current input character. */
Chris Allegretta's avatar
Chris Allegretta committed
145
    char *buf;
146
147
148
149
150
151
152
153
	/* The buffer where we store chunks of the file. */
    filestruct *fileptr = current;
	/* The current line of the file. */
    bool first_line_ins = FALSE;
	/* Whether we're inserting with the cursor on the first line. */
    int input_int;
	/* The current value we read from the file, whether an input
	 * character or EOF. */
154
#ifndef NANO_SMALL
155
156
    int fileformat = 0;
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
157
#endif
Chris Allegretta's avatar
Chris Allegretta committed
158

159
    buf = charalloc(bufx);
160
    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
161

162
163
    if (current != NULL) {
	if (current == fileage)
164
	    first_line_ins = TRUE;
165
166
	else
	    fileptr = current->prev;
Chris Allegretta's avatar
Chris Allegretta committed
167
    }
168

169
170
    /* For the assertion in read_line(), it must be true that if current
     * is NULL, then so is fileage. */
171
172
    assert(current != NULL || fileage == NULL);

173
    /* Read the entire file into the file struct. */
174
    while ((input_int = getc(f)) != EOF) {
175
	input = (char)input_int;
Chris Allegretta's avatar
Chris Allegretta committed
176

177
178
	/* If it's a *nix file (LF) or a DOS file (CR LF), and file
	 * conversion isn't disabled, handle it! */
179
	if (input == '\n') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
180

181
182
183
184
185
186
187
188
189
190
191
#ifndef NANO_SMALL
	    /* If there's a CR before the LF, set fileformat to DOS if
	     * we currently think this is a *nix file, or to both if we
	     * currently think it's a Mac file. */
	    if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r' &&
		(fileformat == 0 || fileformat == 2))
		fileformat++;
#endif

	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
192

193
194
	    /* Reset the line length in preparation for the next
	     * line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
195
196
	    len = 0;

197
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
198
	    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
199
	    i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
200
#ifndef NANO_SMALL
201
202
	/* If it's a Mac file (CR without an LF), and file conversion
	 * isn't disabled, handle it! */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
203
204
	} else if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r') {

205
206
207
208
209
210
211
212
	    /* If we currently think the file is a *nix file, set
	     * fileformat to Mac.  If we currently think the file is a
	     * DOS file, set fileformat to both DOS and Mac. */
	    if (fileformat == 0 || fileformat == 1)
		fileformat += 2;

	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
213

214
215
216
	    /* Reset the line length in preparation for the next line.
	     * Since we've already read in the next character, reset it
	     * to 1 instead of 0. */
217
	    len = 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
218

Chris Allegretta's avatar
Chris Allegretta committed
219
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
220
	    totsize++;
221
	    buf[0] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
222
	    buf[1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
223
224
	    i = 1;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
225
	} else {
226
227
	    /* Calculate the total length of the line.  It might have
	     * nulls in it, so we can't just use strlen() here. */
228
229
	    len++;

Chris Allegretta's avatar
Chris Allegretta committed
230
	    /* Now we allocate a bigger buffer 128 characters at a time.
231
232
233
	     * If we allocate a lot of space for one line, we may indeed
	     * have to use a buffer this big later on, so we don't
	     * decrease it at all.  We do free it at the end, though. */
Chris Allegretta's avatar
Chris Allegretta committed
234
235
	    if (i >= bufx - 1) {
		bufx += 128;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
236
		buf = charealloc(buf, bufx);
Chris Allegretta's avatar
Chris Allegretta committed
237
	    }
238
	    buf[i] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
239
	    buf[i + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
240
241
	    i++;
	}
242
243
244
	totsize++;
    }

245
246
    /* This conditional duplicates previous read_byte() behavior.
     * Perhaps this could use some better handling. */
247
248
249
    if (ferror(f))
	nperror(filename);
    fclose(f);
Chris Allegretta's avatar
Chris Allegretta committed
250

251
#ifndef NANO_SMALL
252
253
254
    /* If file conversion isn't disabled and the last character in this
     * file is a CR, read it in properly as a Mac format line. */
    if (len == 0 && !ISSET(NO_CONVERT) && input == '\r') {
255
256
257
	buf[0] = input;
	buf[1] = '\0';
	len = 1;
258
259
    }
#endif
260

261
262
    /* Did we not get a newline and still have stuff to do? */
    if (len > 0) {
263
#ifndef NANO_SMALL
264
265
266
267
268
269
270
	/* If file conversion isn't disabled and the last character in
	 * this file is a CR, set fileformat to Mac if we currently
	 * think the file is a *nix file, or to both DOS and Mac if we
	 * currently think the file is a DOS file. */
	if (!ISSET(NO_CONVERT) && buf[len - 1] == '\r' &&
		(fileformat == 0 || fileformat == 1))
	    fileformat += 2;
271
272
#endif

273
274
275
276
277
278
279
280
281
	/* Read in the last line properly. */
	fileptr = read_line(buf, fileptr, &first_line_ins, len);
	num_lines++;
	totsize++;
    }
    free(buf);

    /* If we didn't get a file and we don't already have one, make a new
     * file. */
282
    if (totsize == 0 || fileptr == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
283
	new_file();
Robert Siemborski's avatar
Robert Siemborski committed
284

285
    /* Did we try to insert a file of 0 bytes? */
286
287
288
289
290
291
292
293
294
295
296
297
    if (num_lines != 0) {
	if (current != NULL) {
	    fileptr->next = current;
	    current->prev = fileptr;
	    renumber(current);
	    current_x = 0;
	    placewewant = 0;
	} else if (fileptr->next == NULL) {
	    filebot = fileptr;
	    new_magicline();
	    totsize--;
	}
Chris Allegretta's avatar
Chris Allegretta committed
298
    }
299
300

#ifndef NANO_SMALL
301
302
303
304
305
306
307
308
309
    if (fileformat == 3)
	statusbar(
		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);
    else if (fileformat == 2)
	statusbar(P_("Read %lu line (Converted from Mac format)",
		"Read %lu lines (Converted from Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
310
    else if (fileformat == 1)
311
312
313
	statusbar(P_("Read %lu line (Converted from DOS format)",
		"Read %lu lines (Converted from DOS format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
314
315
    else
#endif
316
317
	statusbar(P_("Read %lu line", "Read %lu lines",
		(unsigned long) num_lines),(unsigned long)num_lines);
318

319
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
320
321
}

322
323
324
325
326
327
328
329
/* Open the file (and decide if it exists).  If newfie is TRUE, display
 * "New File" if the file is missing.  Otherwise, say "[filename] not
 * found".
 *
 * Return -2 if we say "New File".  Otherwise, -1 if the file isn't
 * opened, 0 otherwise.  The file might still have an error while
 * reading with a 0 return value.  *f is set to the opened file. */
int open_file(const char *filename, bool newfie, FILE **f)
Chris Allegretta's avatar
Chris Allegretta committed
330
331
332
333
{
    int fd;
    struct stat fileinfo;

334
335
336
337
    assert(f != NULL);
    if (filename == NULL || filename[0] == '\0' ||
	    stat(filename, &fileinfo) == -1) {
	if (newfie) {
Chris Allegretta's avatar
Chris Allegretta committed
338
	    statusbar(_("New File"));
339
	    return -2;
Chris Allegretta's avatar
Chris Allegretta committed
340
	}
341
342
	statusbar(_("\"%s\" not found"), filename);
	return -1;
343
344
345
346
347
    } else if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
		S_ISBLK(fileinfo.st_mode)) {
	/* Don't open character or block files.  Sorry, /dev/sndstat! */
	statusbar(S_ISDIR(fileinfo.st_mode) ? _("\"%s\" is a directory") :
			_("File \"%s\" is a device file"), filename);
348
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
349
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
350
351
352
353
354
355
356
357
	statusbar(_("Error reading %s: %s"), filename, strerror(errno));
 	return -1;
     } else {
	/* File is A-OK. */
	*f = fdopen(fd, "rb"); /* Binary for our own line-end munging */

	if (*f == NULL) {
	    statusbar(_("Error reading %s: %s"), filename, strerror(errno));
358
	    close(fd);
359
360
	} else
	    statusbar(_("Reading File"));
Chris Allegretta's avatar
Chris Allegretta committed
361
    }
362
    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
363
364
}

365
/* This function will return the name of the first available extension
366
367
 * of a filename (starting with filename.save, then filename.save.1,
 * etc.).  Memory is allocated for the return value.  If no writable
368
 * extension exists, we return "". */
Chris Allegretta's avatar
Chris Allegretta committed
369
char *get_next_filename(const char *name)
370
371
{
    int i = 0;
372
373
    char *buf;
    size_t namelen = strlen(name);
374

375
    buf = charalloc(namelen + num_of_digits(INT_MAX) + 7);
376
    strcpy(buf, name);
377
378
    strcpy(buf + namelen, ".save");
    namelen += 5;
379

380
    while (TRUE) {
381
	struct stat fs;
382
383

	if (stat(buf, &fs) == -1)
Chris Allegretta's avatar
Chris Allegretta committed
384
	    return buf;
385
386
387
388
	if (i == INT_MAX)
	    break;

	i++;
389
	sprintf(buf + namelen, ".%d", i);
390
391
    }

Chris Allegretta's avatar
Chris Allegretta committed
392
    /* We get here only if there is no possible save file. */
393
    null_at(&buf, 0);
394
395
396
    return buf;
}

397
398
#ifndef NANO_SMALL
void execute_command(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
399
{
400
401
402
403
404
405
406
#ifdef ENABLE_MULTIBUFFER
    if (ISSET(MULTIBUFFER)) {
	/* Update the current entry in the open_files structure. */
	add_open_file(TRUE);
	new_file();
	UNSET(MODIFIED);
	UNSET(MARK_ISSET);
407
    }
408
409
410
411
412
413
414
415
416
#endif /* ENABLE_MULTIBUFFER */
    open_pipe(command);
#ifdef ENABLE_MULTIBUFFER
    /* Add this new entry to the open_files structure. */
    if (ISSET(MULTIBUFFER))
	load_file();
#endif /* ENABLE_MULTIBUFFER */
}
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
417

418
419
420
421
422
423
424
/* name is a file name to open.  We make a new buffer if necessary, then
 * open and read the file. */
void load_buffer(const char *name)
{
    bool new_buffer = fileage == NULL
#ifdef ENABLE_MULTIBUFFER
	 || ISSET(MULTIBUFFER)
425
#endif
426
427
428
429
430
431
432
433
434
	;
	/* new_buffer says whether we load to this buffer or a new one.
	 * If new_buffer is TRUE, we display "New File" if the file is
	 * not found, and if it is found we set filename and add a new
	 * open_files entry. */
    FILE *f;
    int rc;
	/* rc == -2 means that the statusbar displayed "New File".  -1
	 * means that the open failed.  0 means success. */
435

436
437
438
439
440
#ifndef DISABLE_OPERATINGDIR
    if (check_operating_dir(name, FALSE)) {
	statusbar(_("Can't insert file from outside of %s"), operating_dir);
	return;
    }
441
442
#endif

443
444
445
#ifdef ENABLE_MULTIBUFFER
    /* Update the current entry in the open_files structure. */
    add_open_file(TRUE);
446
447
#endif

448
449
450
451
452
    rc = open_file(name, new_buffer, &f);

#ifdef ENABLE_MULTIBUFFER
    if (rc != -1 && ISSET(MULTIBUFFER)) {
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
453
#ifndef NANO_SMALL
454
	UNSET(MARK_ISSET);
Chris Allegretta's avatar
Chris Allegretta committed
455
#endif
456
    }
457
#endif
458
459
460
461
462
463
464
465

    if (rc != -1 && new_buffer) {
	filename = mallocstrcpy(filename, name);
	new_file();
    }

    if (rc == 0) {
	read_file(f, filename);
Chris Allegretta's avatar
Chris Allegretta committed
466
#ifndef NANO_SMALL
467
	stat(filename, &originalfilestat);
Chris Allegretta's avatar
Chris Allegretta committed
468
#endif
469
470
471
472
473
474
475
476
477
478
479
480
    }

    /* Add this new entry to the open_files structure if we have
     * multibuffer support, or to the main filestruct if we don't. */
    if (rc != -1 && new_buffer)
	load_file();
}

void do_insertfile(void)
{
    int i;
    const char *msg;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
481
    char *ans = mallocstrcpy(NULL, "");
482
483
	/* The last answer the user typed on the statusbar.  Saved for if
	 * they do M-F or cancel the file browser. */
484
485
486
#ifndef NANO_SMALL
    bool extcmd = FALSE;
#endif
487

488
489
490
    wrap_reset();

#if !defined(DISABLE_BROWSER) || (!defined(NANO_SMALL) && defined(ENABLE_MULTIBUFFER))
491
  start_again:
492
493
#endif

494
495
#ifndef NANO_SMALL
    if (extcmd) {
496
#ifdef ENABLE_MULTIBUFFER
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
	if (ISSET(MULTIBUFFER))
	    msg = N_("Command to execute in new buffer [from %s] ");
	else
#endif
	    msg = N_("Command to execute [from %s] ");
    } else {
#endif
#ifdef ENABLE_MULTIBUFFER
	if (ISSET(MULTIBUFFER)) {
	    msg = N_("File to insert into new buffer [from %s] ");
	} else
#endif
	    msg = N_("File to insert [from %s] ");
#ifndef NANO_SMALL
	}
512
#endif
513

514
515
516
517
518
    i = statusq(TRUE,
#ifndef NANO_SMALL
		extcmd ? extcmd_list :
#endif
		insertfile_list, ans,
Chris Allegretta's avatar
Chris Allegretta committed
519
#ifndef NANO_SMALL
520
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
521
#endif
522
523
524
525
		_(msg),
#ifndef DISABLE_OPERATINGDIR
		operating_dir != NULL && strcmp(operating_dir, ".") != 0 ?
		operating_dir :
526
#endif
527
		"./");
528

Chris Allegretta's avatar
Chris Allegretta committed
529
    if (i != -1) {
530
531
	int old_current_x = current_x;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
532
	ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
533

534
#if !defined(NANO_SMALL) && defined(ENABLE_MULTIBUFFER)
535
	if (i == TOGGLE_MULTIBUFFER_KEY) {
536
537
	    /* Don't allow toggling if we're in view mode. */
	    if (!ISSET(VIEW_MODE))
538
		TOGGLE(MULTIBUFFER);
539
	    goto start_again;
540
	}
541
#endif
542

543
544
545
546
#ifndef DISABLE_BROWSER
	if (i == NANO_TOFILES_KEY) {
	    char *tmp = do_browse_from(answer);

547
	    if (tmp == NULL)
548
		goto start_again;
549
550
	    free(answer);
	    answer = tmp;
Chris Allegretta's avatar
Chris Allegretta committed
551
552
553
	}
#endif

554
#ifndef NANO_SMALL
555
556
557
558
559
560
	if (i == NANO_TOOTHERINSERT_KEY) {
	    extcmd = !extcmd;
	    goto start_again;
	}

	if (extcmd)
561
562
	    execute_command(answer);
	else {
563
#endif
564
565
	    answer = mallocstrassn(answer, real_dir_from_tilde(answer));
	    load_buffer(answer);
566
#ifndef NANO_SMALL
567
	}
568
#endif
569

570
#ifdef ENABLE_MULTIBUFFER
571
572
573
	if (ISSET(MULTIBUFFER)) {
	    /* Update the titlebar. */
	    titlebar(NULL);
574

575
576
577
	    /* Reinitialize the shortcut list. */
	    shortcut_init(FALSE);
	} else {
578
#endif
579
	    /* Mark the file as modified. */
580
	    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
581

582
583
	    /* Restore the old x-coordinate position. */
	    current_x = old_current_x;
584
#ifdef ENABLE_MULTIBUFFER
585
	}
Chris Allegretta's avatar
Chris Allegretta committed
586
587
#endif

588
	/* Refresh the screen. */
589
	edit_refresh();
590
    } else
Chris Allegretta's avatar
Chris Allegretta committed
591
	statusbar(_("Cancelled"));
Chris Allegretta's avatar
Chris Allegretta committed
592

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
593
    free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
594
595
}

596
void do_insertfile_void(void)
597
{
598
#ifdef ENABLE_MULTIBUFFER
599
600
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
	statusbar(_("Key illegal in non-multibuffer mode"));
601
    else
602
#endif
603
	do_insertfile();
604
605
606
607

    display_main_list();
}

608
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
609
610
611
/* Create a new openfilestruct node. */
openfilestruct *make_new_opennode(openfilestruct *prevnode)
{
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
612
    openfilestruct *newnode = (openfilestruct *)nmalloc(sizeof(openfilestruct));
Chris Allegretta's avatar
Chris Allegretta committed
613
614
615
616
617
618
619
620
621
622
623
624
625

    newnode->filename = NULL;
    newnode->fileage = NULL;
    newnode->filebot = NULL;

    newnode->prev = prevnode;
    newnode->next = NULL;

    return newnode;
}

/* Splice a node into an existing openfilestruct. */
void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
626
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
{
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

/* Unlink a node from the rest of the openfilestruct. */
void unlink_opennode(const openfilestruct *fileptr)
{
    assert(fileptr != NULL);

    if (fileptr->prev != NULL)
	fileptr->prev->next = fileptr->next;

    if (fileptr->next != NULL)
	fileptr->next->prev = fileptr->prev;
}

/* Delete a node from the openfilestruct. */
void delete_opennode(openfilestruct *fileptr)
{
    if (fileptr != NULL) {
	if (fileptr->filename != NULL)
	    free(fileptr->filename);
	if (fileptr->fileage != NULL)
	    free_filestruct(fileptr->fileage);
	free(fileptr);
    }
}

/* Deallocate all memory associated with this and later files,
 * including the lines of text. */
void free_openfilestruct(openfilestruct *src)
{
    if (src != NULL) {
	while (src->next != NULL) {
	    src = src->next;
	    delete_opennode(src->prev);
#ifdef DEBUG
668
	    fprintf(stderr, "%s: free'd a node, YAY!\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
669
670
671
672
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
673
	fprintf(stderr, "%s: free'd last node.\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
674
675
676
677
#endif
    }
}

678
/*
679
 * Add/update an entry to the open_files openfilestruct.  If update is
680
681
 * FALSE, a new entry is created; otherwise, the current entry is
 * updated.
682
 */
683
void add_open_file(int update)
684
{
685
    openfilestruct *tmp;
686

687
    if (fileage == NULL || current == NULL || filename == NULL)
688
	return;
689
690

    /* if no entries, make the first one */
691
    if (open_files == NULL)
692
	open_files = make_new_opennode(NULL);
693
694
695
696
697
698
699

    else if (!update) {

	/* otherwise, if we're not updating, make a new entry for
	   open_files and splice it in after the current one */

#ifdef DEBUG
700
	fprintf(stderr, "filename is %s\n", open_files->filename);
701
702
#endif

703
704
	tmp = make_new_opennode(NULL);
	splice_opennode(open_files, tmp, open_files->next);
705
706
707
708
	open_files = open_files->next;
    }

    /* save current filename */
709
    open_files->filename = mallocstrcpy(open_files->filename, filename);
710

711
712
713
714
715
#ifndef NANO_SMALL
    /* save the file's stat */
    open_files->originalfilestat = originalfilestat;
#endif

716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
    /* save current total number of lines */
    open_files->file_totlines = totlines;

    /* save current total size */
    open_files->file_totsize = totsize;

    /* save current x-coordinate position */
    open_files->file_current_x = current_x;

    /* save current y-coordinate position */
    open_files->file_current_y = current_y;

    /* save current place we want */
    open_files->file_placewewant = placewewant;

    /* save current line number */
732
    open_files->file_lineno = current->lineno;
733

734
735
736
737
    /* start with default modification status: unmodified (and marking
       status, if available: unmarked) */
    open_files->file_flags = 0;

Chris Allegretta's avatar
Chris Allegretta committed
738
739
740
    /* if we're updating, save current modification status (and marking
       status, if available) */
    if (update) {
741
742
	if (ISSET(MODIFIED))
	    open_files->file_flags |= MODIFIED;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
743
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
744
745
746
	if (ISSET(MARK_ISSET)) {
	    open_files->file_mark_beginbuf = mark_beginbuf;
	    open_files->file_mark_beginx = mark_beginx;
747
	    open_files->file_flags |= MARK_ISSET;
Chris Allegretta's avatar
Chris Allegretta committed
748
749
750
751
	}
#endif
    }

752
753
754
    /* if we're not in view mode and not updating, the file contents
       might have changed, so save the filestruct; otherwise, don't */
    if (!(ISSET(VIEW_MODE) && !update)) {
Chris Allegretta's avatar
Chris Allegretta committed
755
756
757
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
758
    }
759
760

#ifdef DEBUG
761
    fprintf(stderr, "filename is %s\n", open_files->filename);
762
763
764
765
766
#endif
}

/*
 * Read the current entry in the open_files structure and set up the
767
 * currently open file using that entry's information.
768
 */
769
void load_open_file(void)
770
{
771
    if (open_files == NULL)
772
	return;
773
774
775

    /* set up the filename, the file buffer, the total number of lines in
       the file, and the total file size */
776
    filename = mallocstrcpy(filename, open_files->filename);
777
778
779
#ifndef NANO_SMALL
    originalfilestat = open_files->originalfilestat;
#endif
780
    fileage = open_files->fileage;
781
    current = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
782
    filebot = open_files->filebot;
783
784
785
    totlines = open_files->file_totlines;
    totsize = open_files->file_totsize;

Chris Allegretta's avatar
Chris Allegretta committed
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
    /* restore modification status */
    if (open_files->file_flags & MODIFIED)
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

#ifndef NANO_SMALL
    /* restore marking status */
    if (open_files->file_flags & MARK_ISSET) {
	mark_beginbuf = open_files->file_mark_beginbuf;
	mark_beginx = open_files->file_mark_beginx;
	SET(MARK_ISSET);
    } else
	UNSET(MARK_ISSET);
#endif
801

Chris Allegretta's avatar
Chris Allegretta committed
802
803
804
805
#ifdef ENABLE_COLOR
    update_color();
#endif

806
807
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
808
809
    do_gotopos(open_files->file_lineno, open_files->file_current_x,
	open_files->file_current_y, open_files->file_placewewant);
810

Chris Allegretta's avatar
Chris Allegretta committed
811
    /* update the titlebar */
812
813
814
815
816
817
    clearok(topwin, FALSE);
    titlebar(NULL);
}

/*
 * Open the previous entry in the open_files structure.  If closing_file
818
819
820
 * is FALSE, update the current entry before switching from it.
 * Otherwise, we are about to close that entry, so don't bother doing
 * so.
821
 */
822
void open_prevfile(int closing_file)
823
{
824
    if (open_files == NULL)
825
	return;
826
827

    /* if we're not about to close the current entry, update it before
828
       doing anything */
829
    if (!closing_file)
830
	add_open_file(TRUE);
831

832
    if (open_files->prev == NULL && open_files->next == NULL) {
833
834
835

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
836
	    statusbar(_("No more open file buffers"));
837
	return;
838
839
    }

840
    if (open_files->prev != NULL) {
841
842
843
	open_files = open_files->prev;

#ifdef DEBUG
844
	fprintf(stderr, "filename is %s\n", open_files->filename);
845
846
847
848
#endif

    }

849
    else if (open_files->next != NULL) {
850
851

	/* if we're at the beginning, wrap around to the end */
852
	while (open_files->next != NULL)
853
854
855
	    open_files = open_files->next;

#ifdef DEBUG
856
	    fprintf(stderr, "filename is %s\n", open_files->filename);
857
858
859
860
861
862
#endif

    }

    load_open_file();

863
    statusbar(_("Switched to %s"),
864
865
      ((open_files->filename[0] == '\0') ? "New Buffer" :
	open_files->filename));
866

867
868
869
870
871
#ifdef DEBUG
    dump_buffer(current);
#endif
}

872
void open_prevfile_void(void)
873
{
874
    open_prevfile(FALSE);
875
876
}

877
878
/*
 * Open the next entry in the open_files structure.  If closing_file is
879
880
 * FALSE, update the current entry before switching from it.  Otherwise,
 * we are about to close that entry, so don't bother doing so.
881
 */
882
void open_nextfile(int closing_file)
883
{
884
    if (open_files == NULL)
885
	return;
886
887

    /* if we're not about to close the current entry, update it before
888
       doing anything */
889
    if (!closing_file)
890
	add_open_file(TRUE);
891

892
    if (open_files->prev == NULL && open_files->next == NULL) {
893
894
895

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
896
	    statusbar(_("No more open file buffers"));
897
	return;
898
899
    }

900
    if (open_files->next != NULL) {
901
902
903
	open_files = open_files->next;

#ifdef DEBUG
904
	fprintf(stderr, "filename is %s\n", open_files->filename);
905
906
907
#endif

    }
908
    else if (open_files->prev != NULL) {
909
910

	/* if we're at the end, wrap around to the beginning */
911
	while (open_files->prev != NULL) {
912
913
914
	    open_files = open_files->prev;

#ifdef DEBUG
915
	    fprintf(stderr, "filename is %s\n", open_files->filename);
916
917
918
919
920
921
922
#endif

	}
    }

    load_open_file();

923
    statusbar(_("Switched to %s"),
924
925
      ((open_files->filename[0] == '\0') ? "New Buffer" :
	open_files->filename));
926

927
928
929
930
931
#ifdef DEBUG
    dump_buffer(current);
#endif
}

932
void open_nextfile_void(void)
933
{
934
    open_nextfile(FALSE);
935
936
}

937
938
/*
 * Delete an entry from the open_files filestruct.  After deletion of an
939
 * entry, the next or previous entry is opened, whichever is found first.
940
941
942
943
 * Return 0 on success or 1 on error.
 */
int close_open_file(void)
{
944
    openfilestruct *tmp;
945

946
    if (open_files == NULL)
947
948
	return 1;

Chris Allegretta's avatar
Chris Allegretta committed
949
950
951
    /* make sure open_files->fileage and fileage, and open_files->filebot
       and filebot, are in sync; they might not be if lines have been cut
       from the top or bottom of the file */
952
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
953
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
954

955
    tmp = open_files;
956
957
958
959
960
961
    if (open_files->next != NULL)
	open_nextfile(TRUE);
    else if (open_files->prev != NULL)
	open_prevfile(TRUE);
    else
	return 1;
962

963
964
    unlink_opennode(tmp);
    delete_opennode(tmp);
965

966
    shortcut_init(FALSE);
967
968
969
    display_main_list();
    return 0;
}
970
#endif /* ENABLE_MULTIBUFFER */
971

972
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR) || !defined(NANO_SMALL)
973
/*
974
975
976
977
978
979
 * When passed "[relative path]" or "[relative path][filename]" in
 * origpath, return "[full path]" or "[full path][filename]" on success,
 * or NULL on error.  This is still done if the file doesn't exist but
 * the relative path does (since the file could exist in memory but not
 * yet on disk); it is not done if the relative path doesn't exist (since
 * the first call to chdir() will fail then).
980
 */
981
char *get_full_path(const char *origpath)
982
{
983
984
985
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
986
    char *expanded_origpath;
987

988
    /* first, get the current directory, and tack a slash onto the end of
989
       it, unless it turns out to be "/", in which case leave it alone */
990
991
992

    d_here = getcwd(NULL, PATH_MAX + 1);

993
    if (d_here != NULL) {
994
995

	align(&d_here);
996
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
997
	    d_here = charealloc(d_here, strlen(d_here) + 2);
998
999
	    strcat(d_here, "/");
	}
1000
1001
1002
1003
1004

	/* stat origpath; if stat() fails, assume that origpath refers to
	   a new file that hasn't been saved to disk yet (i. e. set
	   path_only to 0); if stat() succeeds, set path_only to 0 if
	   origpath doesn't refer to a directory, or to 1 if it does */
1005
	path_only = !stat(origpath, &fileinfo) && S_ISDIR(fileinfo.st_mode);
1006

1007
	expanded_origpath = real_dir_from_tilde(origpath);
1008
	/* save the value of origpath in both d_there and d_there_file */
1009
1010
1011
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1012

1013
1014
1015
1016
1017
1018
	/* if we have a path but no filename, tack slashes onto the ends
	   of both d_there and d_there_file, if they don't end in slashes
	   already */
	if (path_only) {
	    tmp = d_there[strlen(d_there) - 1];
	    if (tmp != '/') {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1019
		d_there = charealloc(d_there, strlen(d_there) + 2);
1020
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1021
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1022
1023
1024
1025
		strcat(d_there_file, "/");
	    }
	}

1026
1027
1028
1029
1030
	/* search for the last slash in d_there */
	last_slash = strrchr(d_there, '/');

	/* if we didn't find one, copy d_here into d_there; all data is
	   then set up */
1031
	if (last_slash == NULL)
1032
	    d_there = mallocstrcpy(d_there, d_here);
1033
	else {
1034
1035
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1036
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1037
	    null_at(&d_there, last_slash_index + 1);
1038
1039
1040

	    /* and remove all non-file elements from d_there_file (i. e.
	       everything before and including the last slash); if we
1041
	       have a path but no filename, don't do anything */
1042
1043
1044
1045
1046
1047
	    if (!path_only) {
		last_slash = strrchr(d_there_file, '/');
		last_slash++;
		strcpy(d_there_file, last_slash);
		align(&d_there_file);
	    }
1048
1049
1050

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1051
1052
1053
		/* get the full pathname, and save it back in d_there,
		   tacking a slash on the end if we have a path but no
		   filename; if the saving fails, get out */
1054
1055
1056
1057
1058
1059

		free(d_there);

		d_there = getcwd(NULL, PATH_MAX + 1);

		align(&d_there);
1060
		if (d_there != NULL) {
1061
1062
1063

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
1064
		    if (strcmp(d_there, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1065
			d_there = charealloc(d_there, strlen(d_there) + 2);
1066
1067
			strcat(d_there, "/");
		    }
1068
1069
1070
		}
		else
		    return NULL;
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
	    }

	    /* finally, go back to where we were before, d_here (no error
	       checking is done on this chdir(), because we can do
	       nothing if it fails) */
	    chdir(d_here);
	}
	
	/* all data is set up; fill in newpath */

1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
	/* if we have a path and a filename, newpath = d_there +
	   d_there_file; otherwise, newpath = d_there */
	if (!path_only) {
	    newpath = charalloc(strlen(d_there) + strlen(d_there_file) + 1);
	    strcpy(newpath, d_there);
	    strcat(newpath, d_there_file);
	}
	else {
	    newpath = charalloc(strlen(d_there) + 1);
	    strcpy(newpath, d_there);
	}
1092
1093
1094
1095
1096
1097
1098
1099
1100

	/* finally, clean up */
	free(d_there_file);
	free(d_there);
	free(d_here);
    }

    return newpath;
}
1101
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1102
1103
1104

#ifndef DISABLE_SPELLER
/*
1105
1106
1107
 * This function accepts a path and returns the full path (via
 * get_full_path()).  On error, if the path doesn't reference a
 * directory, or if the directory isn't writable, it returns NULL.
1108
 */
1109
char *check_writable_directory(const char *path)
1110
{
1111
    char *full_path = get_full_path(path);
1112
    int writable;
1113
1114
    struct stat fileinfo;

1115
    /* if get_full_path() failed, return NULL */
1116
    if (full_path == NULL)
1117
	return NULL;
1118
1119
1120

    /* otherwise, stat() the full path to see if it's writable by the
       user; set writable to 1 if it is, or 0 if it isn't */
1121
    writable = !stat(full_path, &fileinfo) && (fileinfo.st_mode & S_IWUSR);
1122
1123

    /* if the full path doesn't end in a slash (meaning get_full_path()
1124
1125
1126
1127
       found that it isn't a directory) or isn't writable, free full_path
       and return NULL */
    if (full_path[strlen(full_path) - 1] != '/' || writable == 0) {
	free(full_path);
1128
	return NULL;
1129
    }
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139

    /* otherwise, return the full path */
    return full_path;
}

/*
 * This function accepts a directory name and filename prefix the same
 * way that tempnam() does, determines the location for its temporary
 * file the same way that tempnam() does, safely creates the temporary
 * file there via mkstemp(), and returns the name of the temporary file
1140
1141
1142
1143
1144
1145
1146
 * the same way that tempnam() does.  It does not reference the value of
 * TMP_MAX because the total number of random filenames that it can
 * generate using one prefix is equal to 256**6, which is a sufficiently
 * large number to handle most cases.  Since the behavior after tempnam()
 * generates TMP_MAX random filenames is implementation-defined, my
 * implementation is to go on generating random filenames regardless of
 * it.
1147
 */
1148
1149
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1150
1151
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1152
    int filedesc;
1153

1154
1155
      /* if $TMPDIR is set and non-empty, set tempdir to it, run it through
         get_full_path(), and save the result in full_tempdir; otherwise,
1156
         leave full_tempdir set to NULL */
1157
    TMPDIR_env = getenv("TMPDIR");
1158
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1159
	full_tempdir = check_writable_directory(TMPDIR_env);
1160

1161
1162
1163
    /* if $TMPDIR is blank or isn't set, or isn't a writable
       directory, and dirname isn't NULL, try it; otherwise, leave
       full_tempdir set to NULL */
1164
    if (full_tempdir == NULL && dirname != NULL)
1165
	full_tempdir = check_writable_directory(dirname);
1166
1167
1168

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1169
    if (full_tempdir == NULL)
1170
	full_tempdir = check_writable_directory(P_tmpdir);
1171
1172

    /* if P_tmpdir didn't work, use /tmp instead */
1173
    if (full_tempdir == NULL) {
1174
1175
1176
1177
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1178
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1179
1180

    /* like tempnam(), use only the first 5 characters of the prefix */
1181
1182
1183
1184
1185
1186
1187
1188
    strncat(full_tempdir, filename_prefix, 5);
    strcat(full_tempdir, "XXXXXX");
    filedesc = mkstemp(full_tempdir);

    /* if mkstemp succeeded, close the resulting file; afterwards, it'll be
       0 bytes long, so delete it; finally, return the filename (all that's
       left of it) */
    if (filedesc != -1) {
1189
	close(filedesc);
1190
1191
	unlink(full_tempdir);
	return full_tempdir;
1192
    }
1193
1194
1195

    free(full_tempdir);
    return NULL;
1196
1197
}
#endif /* !DISABLE_SPELLER */
1198
1199

#ifndef DISABLE_OPERATINGDIR
1200
1201
1202
1203
1204
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1205
    if (operating_dir == NULL)
1206
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1207

1208
1209
1210
    full_operating_dir = get_full_path(operating_dir);

    /* If get_full_path() failed or the operating directory is
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1211
     * inaccessible, unset operating_dir. */
1212
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1213
1214
1215
1216
1217
1218
1219
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1220
/* Check to see if we're inside the operating directory.  Return 0 if we
1221
1222
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1223
 * completion will work. */
1224
int check_operating_dir(const char *currpath, int allow_tabcomp)
1225
{
1226
1227
1228
1229
    /* The char *full_operating_dir is global for mem cleanup.  It
     * should have already been initialized by init_operating_dir().
     * Also, a relative operating directory path will only be handled
     * properly if this is done. */
1230

1231
1232
1233
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1234

1235
    /* If no operating directory is set, don't bother doing anything. */
1236
    if (operating_dir == NULL)
1237
	return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1238

1239
    assert(full_operating_dir != NULL);
1240
1241

    fullpath = get_full_path(currpath);
1242
1243
1244
1245
1246
1247
1248
1249

    /* fullpath == NULL means some directory in the path doesn't exist
     * or is unreadable.  If allow_tabcomp is zero, then currpath is
     * what the user typed somewhere.  We don't want to report a
     * non-existent directory as being outside the operating directory,
     * so we return 0.  If allow_tabcomp is nonzero, then currpath
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1250
    if (fullpath == NULL)
1251
	return allow_tabcomp;
1252
1253
1254
1255
1256

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

1257
1258
1259
1260
1261
    /* If both searches failed, we're outside the operating directory.
     * Otherwise, check the search results; if the full operating
     * 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. */
1262
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1263
1264
	retval = 1;
    free(fullpath);	
1265
1266

    /* Otherwise, we're still inside it. */
1267
    return retval;
1268
}
1269
1270
#endif

1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
#ifndef NANO_SMALL
void init_backup_dir(void)
{
    char *full_backup_dir;

    if (backup_dir == NULL)
	return;

    full_backup_dir = get_full_path(backup_dir);

    /* If get_full_path() failed or the backup directory is
     * inaccessible, unset backup_dir. */
    if (full_backup_dir == NULL ||
	full_backup_dir[strlen(full_backup_dir) - 1] != '/') {
	free(full_backup_dir);
	free(backup_dir);
	backup_dir = NULL;
    } else {
	free(backup_dir);
	backup_dir = full_backup_dir;
    }
}
#endif

1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
/* Read from inn, write to out.  We assume inn is opened for reading,
 * and out for writing.  We return 0 on success, -1 on read error, -2 on
 * write error. */
int copy_file(FILE *inn, FILE *out)
{
    char buf[BUFSIZ];
    size_t charsread;
    int retval = 0;

    assert(inn != NULL && out != NULL);
    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);
    if (fclose(inn) == EOF)
	retval = -1;
    if (fclose(out) == EOF)
	retval = -2;
    return retval;
}

1323
/* Write a file out.  If tmp is FALSE, we set the umask to disallow
1324
1325
1326
 * anyone else from accessing the file, we don't set the global variable
 * 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
1327
 *
1328
1329
 * tmp means we are writing a temporary file in a secure fashion.  We
 * use it when spell checking or dumping the file on an error.
1330
 *
1331
1332
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1333
 *
1334
1335
 * nonamechange means don't change the current filename.  It is ignored
 * if tmp is FALSE or if we're appending/prepending.
1336
1337
 *
 * Return -1 on error, 1 on success. */
1338
int write_file(const char *name, int tmp, int append, int nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1339
{
1340
1341
    int retval = -1;
	/* Instead of returning in this function, you should always
1342
1343
1344
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
    const filestruct *fileptr = fileage;
1345
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1346
	/* The file descriptor we use. */
1347
    mode_t original_umask = 0;
1348
1349
	/* Our umask, from when nano started. */
    int realexists;
1350
	/* The result of stat().  TRUE if the file exists, FALSE
1351
	 * otherwise.  If name is a link that points nowhere, realexists
1352
	 * is FALSE. */
1353
1354
1355
    struct stat st;
	/* The status fields filled in by stat(). */
    int anyexists;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1356
	/* The result of lstat().  Same as realexists unless name is a
1357
1358
1359
1360
	 * link. */
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1361
	/* name after tilde expansion. */
1362
1363
1364
1365
    FILE *f;
	/* The actual file, realname, we are writing to. */
    char *tempname = NULL;
	/* The temp file name we write to on prepend. */
Chris Allegretta's avatar
Chris Allegretta committed
1366

1367
    assert(name != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1368
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1369
1370
1371
	statusbar(_("Cancelled"));
	return -1;
    }
1372
1373
    if (!tmp)
	titlebar(NULL);
1374

1375
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1376

1377
#ifndef DISABLE_OPERATINGDIR
1378
    /* If we're writing a temporary file, we're probably going outside
1379
     * the operating directory, so skip the operating directory test. */
1380
    if (!tmp && check_operating_dir(realname, FALSE) != 0) {
1381
1382
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1383
1384
1385
    }
#endif

1386
1387
1388
1389
1390
1391
1392
    anyexists = lstat(realname, &lst) != -1;
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
    /* If NOFOLLOW_SYMLINKS is set, it doesn't make sense to prepend or
     * append to a symlink.  Here we warn about the contradiction. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode)) {
1393
1394
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1395
1396
1397
	goto cleanup_and_exit;
    }

1398
    /* Save the state of file at the end of the symlink (if there is
1399
1400
     * one). */
    realexists = stat(realname, &st) != -1;
1401

1402
1403
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1404
1405
1406
1407
1408
1409
1410
1411
     * 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. */
    if (ISSET(BACKUP_FILE) && !tmp && realexists != 0 &&
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1412
	FILE *backup_file;
1413
	char *backupname;
1414
	struct utimbuf filetime;
1415
	int copy_status;
1416

1417
	/* Save the original file's access and modification times. */
1418
1419
1420
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1421
	/* Open the original file to copy to the backup. */
1422
	f = fopen(realname, "rb");
1423
	if (f == NULL) {
1424
	    statusbar(_("Error reading %s: %s"), realname,
1425
		strerror(errno));
1426
	    goto cleanup_and_exit;
1427
1428
	}

1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
	/* If backup_dir is set, we set backupname to
	 * backup_dir/backupname~, where backupnae 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~. */
	if (backup_dir != NULL) {
	    char *canon_realname = get_full_path(realname);
	    size_t i;

	    if (canon_realname == NULL)
		/* 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~. */
		canon_realname = mallocstrcpy(NULL, tail(realname));
	    else {
		for (i = 0; canon_realname[i] != '\0'; i++) {
		    if (canon_realname[i] == '/')
			canon_realname[i] = '!';
		}
	    }

	    backupname = charalloc(strlen(backup_dir) +
		strlen(canon_realname) + 2);
	    sprintf(backupname, "%s%s~", backup_dir, canon_realname);
	    free(canon_realname);
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1461

1462
1463
1464
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1465
	backup_file = fopen(backupname, "wb");
1466
1467
1468
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
	    statusbar(_("Error writing %s: %s"), backupname, strerror(errno));
1469
	    free(backupname);
1470
1471
1472
1473
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1474
1475
1476
	}

#ifdef DEBUG
1477
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1478
1479
#endif

1480
1481
1482
1483
1484
1485
1486
1487
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
	/* And set metadata. */
	if (copy_status != 0 || chown(backupname, originalfilestat.st_uid,
		originalfilestat.st_gid) == -1 ||
		utime(backupname, &filetime) == -1) {
	    free(backupname);
	    if (copy_status == -1)
1488
		statusbar(_("Error reading %s: %s"), realname, strerror(errno));
1489
1490
1491
1492
1493
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1494
1495
	free(backupname);
    }
1496
#endif /* !NANO_SMALL */
1497

1498
1499
1500
1501
1502
1503
    /* If NOFOLLOW_SYMLINKS and the file is a link, we aren't doing
     * prepend or append.  So we delete the link first, and just
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1504
	goto cleanup_and_exit;
1505
    }
1506

1507
1508
    original_umask = umask(0);
    umask(original_umask);
1509

1510
    /* If we create a temp file, we don't let anyone else access it.  We
1511
     * create a temp file if tmp is TRUE or if we're prepending. */
1512
1513
1514
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

1515
    /* If we're prepending, copy the file to a temp file. */
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
    if (append == 2) {
	int fd_source;
	FILE *f_source = NULL;

	tempname = charalloc(strlen(realname) + 8);
	strcpy(tempname, realname);
	strcat(tempname, ".XXXXXX");
	fd = mkstemp(tempname);
	f = NULL;
	if (fd != -1) {
	    f = fdopen(fd, "wb");
	    if (f == NULL)
		close(fd);
	}
	if (f == NULL) {
	    statusbar(_("Error writing %s: %s"), tempname, strerror(errno));
	    unlink(tempname);
1533
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1534
	}
1535

1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
	fd_source = open(realname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
	}
	if (f_source == NULL) {
	    statusbar(_("Error reading %s: %s"), realname, strerror(errno));
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

	if (copy_file(f_source, f) != 0) {
	    statusbar(_("Error writing %s: %s"), tempname, strerror(errno));
	    unlink(tempname);
1552
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1553
1554
1555
	}
    }

1556
1557
    /* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This is
     * now copied from joe, because wiggy says so *shrug*. */
1558
1559
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
1560
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1561

1562
    /* Set the umask back to the user's original value. */
1563
1564
1565
1566
1567
1568
1569
1570
    umask(original_umask);

    /* First, just give up if we couldn't even open the file. */
    if (fd == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	unlink(tempname);
	goto cleanup_and_exit;
    }
1571

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1572
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1573
    if (f == NULL) {
1574
1575
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1576
	goto cleanup_and_exit;
1577
1578
    }

1579
    /* There might not be a magicline.  There won't be when writing out
1580
1581
1582
1583
1584
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1585

1586
	/* Newlines to nulls, just before we write to disk. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1587
1588
	sunder(fileptr->data);

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

1591
	/* Nulls to newlines; data_len is the string's real length. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1592
1593
	unsunder(fileptr->data, data_len);

1594
	if (size < data_len) {
1595
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1596
	    fclose(f);
1597
	    goto cleanup_and_exit;
1598
	}
1599
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1600
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1601
1602
1603
1604
1605
	    if (putc('\r', f) == EOF) {
		statusbar(_("Error writing %s: %s"), realname, strerror(errno));
		fclose(f);
		goto cleanup_and_exit;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1606
1607

	if (!ISSET(MAC_FILE))
1608
#endif
1609
1610
1611
1612
1613
	    if (putc('\n', f) == EOF) {
		statusbar(_("Error writing %s: %s"), realname, strerror(errno));
		fclose(f);
		goto cleanup_and_exit;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1614
1615
1616
1617
1618

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

1619
    /* If we're prepending, open the temp file, and append it to f. */
1620
    if (append == 2) {
1621
1622
1623
1624
1625
1626
1627
1628
	int fd_source;
	FILE *f_source = NULL;

	fd_source = open(tempname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1629
	}
1630
	if (f_source == NULL) {
1631
1632
	    statusbar(_("Error reading %s: %s"), tempname, strerror(errno));
	    fclose(f);
1633
	    goto cleanup_and_exit;
1634
1635
	}

1636
1637
1638
	if (copy_file(f_source, f) == -1
		|| unlink(tempname) == -1) {
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1639
	    goto cleanup_and_exit;
1640
	}
1641
1642
1643
1644
    } else if (fclose(f) == EOF) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	unlink(tempname);
	goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1645
    }
1646

1647
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1648
	if (!nonamechange) {
1649
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1650
1651
#ifdef ENABLE_COLOR
	    update_color();
1652
1653
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1654
1655
#endif
	}
1656

1657
1658
1659
1660
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1661
1662
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1663
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1664
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1665
    }
1666
1667
1668
1669
1670

    retval = 1;

  cleanup_and_exit:
    free(realname);
1671
    free(tempname);
1672
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1673
1674
}

1675
1676
1677
#ifndef NANO_SMALL
/* Write a marked selection from a file out.  First, set fileage and
 * filebot as the top and bottom of the mark, respectively.  Then call
1678
1679
1680
1681
1682
 * write_file() with the values of name, temp, and append, and with
 * nonamechange set to TRUE so that we don't change the current
 * filename.  Finally, set fileage and filebot back to their old values
 * and return. */
int write_marked(const char *name, int tmp, int append)
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
{
    int retval = -1;
    filestruct *fileagebak = fileage;
    filestruct *filebotbak = filebot;
    int oldmod = ISSET(MODIFIED);
	/* write_file() unsets the MODIFIED flag. */
    size_t topx;
	/* The column of the beginning of the mark. */
    char origchar;
	/* We replace the character at the end of the mark with '\0'.
	 * We save the original character, to restore it. */
    char *origcharloc;
	/* The location of the character we nulled. */

    if (!ISSET(MARK_ISSET))
	return -1;

    /* Set fileage as the top of the mark, and filebot as the bottom. */
    if (current->lineno > mark_beginbuf->lineno ||
		(current->lineno == mark_beginbuf->lineno &&
		current_x > mark_beginx)) {
	fileage = mark_beginbuf;
	topx = mark_beginx;
	filebot = current;
	origcharloc = current->data + current_x;
    } else {
	fileage = current;
	topx = current_x;
	filebot = mark_beginbuf;
	origcharloc = mark_beginbuf->data + mark_beginx;
    }
    origchar = *origcharloc;
    *origcharloc = '\0';
    fileage->data += topx;

    /* If the line at filebot is blank, treat it as the magicline and
     * hence the end of the file.  Otherwise, treat the line after
     * filebot as the end of the file. */
    if (filebot->data[0] != '\0' && filebot->next != NULL)
	filebot = filebot->next;

1724
    retval = write_file(name, tmp, append, TRUE);
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737

    /* Now restore everything. */
    fileage->data -= topx;
    *origcharloc = origchar;
    fileage = fileagebak;
    filebot = filebotbak;
    if (oldmod)
	set_modified();

    return retval;
}
#endif /* !NANO_SMALL */

1738
int do_writeout(int exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1739
{
1740
1741
    int i;
    int append = 0;
1742
#ifdef NANO_EXTRA
1743
    static int did_cred = FALSE;
1744
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1745

1746
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1747
    currshortcut = writefile_list;
1748
1749
#endif

1750
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1751
1752
1753
1754
1755
	i = write_file(filename, FALSE, 0, FALSE);
	if (i == 1) {
	    /* Write succeeded. */
	    display_main_list();
	    return 1;
1756
	}
Chris Allegretta's avatar
Chris Allegretta committed
1757
1758
    }

1759
#ifndef NANO_SMALL
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
    if (ISSET(MARK_ISSET) && !exiting)
	answer = mallocstrcpy(answer, "");
    else
#endif
	answer = mallocstrcpy(answer, filename);

    while (TRUE) {
	const char *msg;
#ifndef NANO_SMALL
	char *ans = mallocstrcpy(NULL, answer);
1770
1771
	const char *formatstr, *backupstr;

1772
	if (ISSET(MAC_FILE))
1773
	   formatstr = N_(" [Mac Format]");
1774
	else if (ISSET(DOS_FILE))
1775
	   formatstr = N_(" [DOS Format]");
1776
1777
1778
	else
	   formatstr = "";

1779
	if (ISSET(BACKUP_FILE))
1780
	   backupstr = N_(" [Backup]");
1781
1782
1783
	else
	   backupstr = "";

1784
	/* Be nice to the translation folks. */
1785
	if (ISSET(MARK_ISSET) && !exiting) {
1786
	    if (append == 2)
1787
		msg = N_("Prepend Selection to File");
Chris Allegretta's avatar
Chris Allegretta committed
1788
	    else if (append == 1)
1789
		msg = N_("Append Selection to File");
1790
	    else
1791
		msg = N_("Write Selection to File");
1792
1793
	} else
#endif /* !NANO_SMALL */
1794
	if (append == 2)
1795
	    msg = N_("File Name to Prepend to");
Chris Allegretta's avatar
Chris Allegretta committed
1796
	else if (append == 1)
1797
	    msg = N_("File Name to Append to");
1798
	else
1799
	    msg = N_("File Name to Write");
1800

1801
1802
1803
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
1804
1805
	i = statusq(!ISSET(RESTRICTED) || filename[0] == '\0',
		writefile_list,
1806
#ifndef NANO_SMALL
1807
		ans, NULL, "%s%s%s", _(msg), formatstr, backupstr
1808
#else
1809
		filename, "%s", _(msg)
1810
1811
1812
1813
1814
1815
#endif
		);

#ifndef NANO_SMALL
	free(ans);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1816

1817
1818
1819
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
1820
	    return -1;
1821
	}
Chris Allegretta's avatar
Chris Allegretta committed
1822

1823
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1824
	if (i == NANO_TOFILES_KEY) {
1825
	    char *tmp = do_browse_from(answer);
1826

1827
	    currshortcut = writefile_list;
1828
1829
1830
1831
	    if (tmp == NULL)
		continue;
	    free(answer);
	    answer = tmp;
1832
	} else
1833
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1834
#ifndef NANO_SMALL
1835
1836
1837
	if (i == TOGGLE_DOS_KEY) {
	    UNSET(MAC_FILE);
	    TOGGLE(DOS_FILE);
1838
	    continue;
1839
1840
1841
	} else if (i == TOGGLE_MAC_KEY) {
	    UNSET(DOS_FILE);
	    TOGGLE(MAC_FILE);
1842
	    continue;
1843
1844
	} else if (i == TOGGLE_BACKUP_KEY) {
	    TOGGLE(BACKUP_FILE);
1845
1846
	    continue;
	} else
1847
#endif /* !NANO_SMALL */
1848
1849
1850
1851
1852
1853
1854
	if (i == NANO_PREPEND_KEY) {
	    append = append == 2 ? 0 : 2;
	    continue;
	} else if (i == NANO_APPEND_KEY) {
	    append = append == 1 ? 0 : 1;
	    continue;
	}
Chris Allegretta's avatar
Chris Allegretta committed
1855

Chris Allegretta's avatar
Chris Allegretta committed
1856
#ifdef DEBUG
1857
	fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1858
#endif
1859
1860

#ifdef NANO_EXTRA
1861
	if (exiting && !ISSET(TEMP_FILE) && strcasecmp(answer, "zzy") == 0
1862
		&& !did_cred) {
1863
	    do_credits();
1864
	    did_cred = TRUE;
1865
1866
	    return -1;
	}
1867
#endif
1868
	if (append == 0 && strcmp(answer, filename) != 0) {
1869
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1870

1871
	    if (!stat(answer, &st)) {
1872
		i = do_yesno(FALSE, _("File exists, OVERWRITE ? "));
1873
1874
		if (i == 0 || i == -1)
		    continue;
1875
1876
1877
1878
1879
	    /* If we're using restricted mode, we aren't allowed to
	     * change the name of a file once it has one because that
	     * would allow reading from or writing to files not
	     * specified on the command line.  In this case, don't
	     * bother showing the "Different Name" prompt. */
1880
	    } else if (!ISSET(RESTRICTED) && filename[0] != '\0'
1881
#ifndef NANO_SMALL
1882
		&& (exiting || !ISSET(MARK_ISSET))
1883
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1884
		) {
1885
		i = do_yesno(FALSE, _("Save file under DIFFERENT NAME ? "));
1886
1887
1888
		if (i == 0 || i == -1)
		    continue;
	    }
1889
	}
Chris Allegretta's avatar
Chris Allegretta committed
1890

1891
#ifndef NANO_SMALL
1892
1893
1894
1895
	/* Here's where we allow the selected text to be written to a
	 * separate file.  If we're using restricted mode, this is
	 * disabled since it allows reading from or writing to files not
	 * specified on the command line. */
1896
	if (!ISSET(RESTRICTED) && !exiting && ISSET(MARK_ISSET))
1897
	    i = write_marked(answer, FALSE, append);
1898
	else
1899
#endif /* !NANO_SMALL */
1900
	    i = write_file(answer, FALSE, append, FALSE);
1901

1902
#ifdef ENABLE_MULTIBUFFER
1903
	/* If we're not about to exit, update the current entry in
1904
	 * the open_files structure. */
1905
	if (!exiting)
1906
	    add_open_file(TRUE);
1907
#endif
1908
1909
	display_main_list();
	return i;
1910
    } /* while (TRUE) */
Chris Allegretta's avatar
Chris Allegretta committed
1911
1912
}

1913
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1914
{
1915
    do_writeout(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
1916
}
Chris Allegretta's avatar
Chris Allegretta committed
1917

1918
/* Return a malloc()ed string containing the actual directory, used
1919
1920
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1921
{
Chris Allegretta's avatar
Chris Allegretta committed
1922
    char *dirtmp = NULL;
1923

1924
    if (buf[0] == '~') {
1925
1926
1927
1928
1929
1930
1931
	size_t i;
	const struct passwd *userdata;

	/* Figure how how much of the str we need to compare */
	for (i = 1; buf[i] != '/' && buf[i] != '\0'; i++)
	    ;

1932
1933
	/* Determine home directory using getpwuid() or getpwent(),
	   don't rely on $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
1934
1935
1936
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
1937
1938
1939
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
1940
			strncmp(userdata->pw_name, buf + 1, i - 1) != 0);
Chris Allegretta's avatar
Chris Allegretta committed
1941
1942
	}
	endpwent();
1943

1944
	if (userdata != NULL) {	/* User found */
1945
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1946
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1947
	}
1948
    }
1949

1950
1951
1952
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1953
    return dirtmp;
1954
1955
}

Chris Allegretta's avatar
Chris Allegretta committed
1956
#ifndef DISABLE_TABCOMP
1957
1958
1959
/* Tack a slash onto the string we're completing if it's a directory.  We
 * assume there is room for one more character on the end of buf.  The
 * return value says whether buf is a directory. */
1960
int append_slash_if_dir(char *buf, bool *lastwastab, int *place)
1961
{
1962
    char *dirptr = real_dir_from_tilde(buf);
1963
    struct stat fileinfo;
1964
    int ret = 0;
1965

1966
    assert(dirptr != buf);
1967

1968
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
1969
	strncat(buf, "/", 1);
1970
	(*place)++;
1971
	/* now we start over again with # of tabs so far */
1972
	*lastwastab = FALSE;
1973
	ret = 1;
1974
1975
    }

1976
    free(dirptr);
1977
    return ret;
1978
}
Chris Allegretta's avatar
Chris Allegretta committed
1979
1980

/*
Chris Allegretta's avatar
Chris Allegretta committed
1981
1982
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1983
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
 *
 * Termios command line History and Editting, originally
 * intended for NetBSD sh (ash)
 * Copyright (c) 1999
 *      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.
 * This code may safely be consumed by a BSD or GPL license.
 */

char **username_tab_completion(char *buf, int *num_matches)
{
2001
    char **matches = (char **)NULL;
2002
    char *matchline = NULL;
2003
    struct passwd *userdata;
2004

2005
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2006
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2007

2008
    strcat(buf, "*");
2009

2010
    while ((userdata = getpwent()) != NULL) {
2011

2012
	if (check_wildcard_match(userdata->pw_name, &buf[1])) {
2013
2014
2015
2016

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2017

2018
2019
2020
2021
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2022
	    if (operating_dir != NULL) {
2023
		if (check_operating_dir(userdata->pw_dir, TRUE) != 0)
2024
2025
2026
2027
		    continue;
	    }
#endif

2028
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2029
2030
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
2031
	    ++(*num_matches);
2032

2033
2034
2035
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2036
	}
2037
2038
    }
    endpwent();
2039

2040
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2041
2042
2043
2044
2045
2046
2047
}

/* This was originally called exe_n_cwd_tab_completion, but we're not
   worried about executables, only filenames :> */

char **cwd_tab_completion(char *buf, int *num_matches)
{
Chris Allegretta's avatar
Chris Allegretta committed
2048
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2049
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2050
2051
2052
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2053
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2054
2055
2056
2057

    /* Stick a wildcard onto the buf, for later use */
    strcat(buf, "*");

2058
    /* Okie, if there's a / in the buffer, strip out the directory part */
2059
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2060
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2061
2062
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2063
	    tmp--;
2064

Chris Allegretta's avatar
Chris Allegretta committed
2065
2066
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2067
2068
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2069

Chris Allegretta's avatar
Chris Allegretta committed
2070
    } else {
2071

Chris Allegretta's avatar
Chris Allegretta committed
2072
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2073
2074
2075
2076
2077
2078
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2079
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2080
2081
2082
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2083

Chris Allegretta's avatar
Chris Allegretta committed
2084
2085
2086
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2087
2088

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2089
    fprintf(stderr, "\nDir = %s\n", dirname);
2090
2091
2092
2093
2094
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2095
    dir = opendir(dirname);
2096
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2097
2098
2099
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2100
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2101
2102
2103
2104
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2105
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2106
2107
#endif
	/* See if this matches */
2108
	if (check_wildcard_match(next->d_name, tmp)) {
2109
2110
2111
2112

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2113
2114
2115
2116
2117
2118
2119
2120

#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match; to
	       properly do operating directory checking, we have to add the
	       directory name to the beginning of the proposed match
	       before we check it */

2121
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2122
2123
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2124
2125
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2126
		if (check_operating_dir(tmp2, TRUE) != 0) {
2127
2128
2129
2130
2131
2132
2133
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2134
	    tmp2 = NULL;
2135
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2136
2137
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2138
	    ++*num_matches;
2139
2140
2141
2142

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2143
2144
	}
    }
2145
2146
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2147

2148
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2149
2150
}

Chris Allegretta's avatar
Chris Allegretta committed
2151
2152
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
2153
2154
char *input_tab(char *buf, int place, bool *lastwastab, int *newplace,
	bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2155
2156
{
    /* Do TAB completion */
2157
    static int num_matches = 0, match_matches = 0;
2158
    static char **matches = (char **)NULL;
2159
    int pos = place, i = 0, col = 0, editline = 0;
2160
    int longestname = 0, is_dir = 0;
2161
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2162

2163
    *list = FALSE;
2164

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2165
    if (*lastwastab == FALSE) {
2166
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2167

2168
	*lastwastab = TRUE;
2169

Chris Allegretta's avatar
Chris Allegretta committed
2170
2171
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2172
2173
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2174

2175
2176
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2177
2178

	/* skip any leading white space */
2179
	while (*tmp && isblank(*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2180
2181
2182
2183
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2184
2185
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2186
	    free(matches);
2187
	    matches = (char **)NULL;
2188
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2189
2190
2191
2192
2193
	}

	/* If the word starts with `~' and there is no slash in the word, 
	 * then try completing this word as a username. */

Chris Allegretta's avatar
Chris Allegretta committed
2194
2195
2196
2197
2198
	/* If the original string begins with a tilde, and the part
	   we're trying to tab-complete doesn't contain a slash, copy
	   the part we're tab-completing into buf, so tab completion
	   will result in buf's containing only the tab-completed
	   username. */
2199
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2200
	    buf = mallocstrcpy(buf, tmp);
2201
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2202
	}
2203
2204
2205
2206
2207
2208
	/* If we're in the middle of the original line, copy the string
	   only up to the cursor position into buf, so tab completion
	   will result in buf's containing only the tab-completed
	   path/filename. */
	else if (strlen(buf) > strlen(tmp))
	    buf = mallocstrcpy(buf, tmp);
Chris Allegretta's avatar
Chris Allegretta committed
2209
2210
2211

	/* Try to match everything in the current working directory that
	 * matches.  */
2212
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2213
2214
2215
	    matches = cwd_tab_completion(tmp, &num_matches);

	/* Don't leak memory */
2216
	free(matchbuf);
Chris Allegretta's avatar
Chris Allegretta committed
2217

2218
2219
2220
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2221
	/* Did we find exactly one match? */
2222
	switch (num_matches) {
2223
	case 0:
2224
	    blank_edit();
2225
	    wrefresh(edit);
2226
2227
	    break;
	case 1:
2228

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2229
	    buf = charealloc(buf, strlen(buf) + strlen(matches[0]) + 1);
2230

2231
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2232
2233
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2234
		tmp++;
2235
	    } else
2236
2237
		tmp = buf;

2238
	    if (strcmp(tmp, matches[0]) == 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2239
		is_dir = append_slash_if_dir(buf, lastwastab, newplace);
2240

2241
	    if (is_dir != 0)
2242
		break;
2243
2244

	    copyto = tmp;
2245
2246
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2247
2248
		tmp++;

2249
	    /* write out the matched name */
2250
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2251
2252
	    *newplace += strlen(matches[0]) - pos;

2253
2254
2255
2256
2257
2258
2259
	    /* if an exact match is typed in and Tab is pressed,
	       *newplace will now be negative; in that case, make it
	       zero, so that the cursor will stay where it is instead of
	       moving backward */
	    if (*newplace < 0)
		*newplace = 0;

2260
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2261
	    append_slash_if_dir(buf, lastwastab, newplace);
2262

2263
2264
	    break;
	default:
2265
	    /* Check to see if all matches share a beginning, and, if so,
2266
	       tack it onto buf and then beep */
2267

2268
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2269
2270
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2271
		tmp++;
2272
	    } else
2273
2274
		tmp = buf;

2275
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2276
		 pos <= strlen(matches[0]); pos++)
2277
2278
		tmp++;

2279
	    while (TRUE) {
2280
2281
2282
2283
2284
2285
2286
2287
		match_matches = 0;

		for (i = 0; i < num_matches; i++) {
		    if (matches[i][pos] == 0)
			break;
		    else if (matches[i][pos] == matches[0][pos])
			match_matches++;
		}
2288
2289
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2290
		    /* All the matches have the same character at pos+1,
2291
		       so paste it into buf... */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2292
		    buf = charealloc(buf, strlen(buf) + 2);
2293
		    strncat(buf, matches[0] + pos, 1);
2294
		    *newplace += 1;
2295
		    pos++;
2296
		} else {
2297
2298
2299
2300
2301
		    beep();
		    break;
		}
	    }
	}
Chris Allegretta's avatar
Chris Allegretta committed
2302
2303
2304
2305
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2306
	if (matches != NULL && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2307
2308
2309
2310
2311

	    /* Blank the edit window, and print the matches out there */
	    blank_edit();
	    wmove(edit, 0, 0);

2312
	    editline = 0;
2313

2314
2315
2316
2317
2318
2319
2320
2321
	    /* Figure out the length of the longest filename */
	    for (i = 0; i < num_matches; i++)
		if (strlen(matches[i]) > longestname)
		    longestname = strlen(matches[i]);

	    if (longestname > COLS - 1)
		longestname = COLS - 1;

2322
	    foo = charalloc(longestname + 5);
2323

Chris Allegretta's avatar
Chris Allegretta committed
2324
2325
	    /* Print the list of matches */
	    for (i = 0, col = 0; i < num_matches; i++) {
2326

2327
		/* make each filename shown be the same length as the longest
2328
		   filename, with two spaces at the end */
2329
2330
2331
2332
2333
2334
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2335
2336
		/* Disable el cursor */
		curs_set(0);
2337
2338
2339
2340
2341
2342
		/* now, put the match on the screen */
		waddnstr(edit, foo, strlen(foo));
		col += strlen(foo);

		/* And if the next match isn't going to fit on the
		   line, move to the next one */
2343
		if (col > COLS - longestname && i + 1 < num_matches) {
2344
2345
		    editline++;
		    wmove(edit, editline, 0);
2346
2347
2348
2349
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2350
2351
2352
		    col = 0;
		}
	    }
2353
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2354
	    wrefresh(edit);
2355
	    *list = TRUE;
2356
2357
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2358
2359
    }

2360
2361
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
2362
    if (*list == FALSE)
2363
	edit_refresh();
2364
2365
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2366
}
2367
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2368

2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
/* Only print the last part of a path; isn't there a shell
 * command for this? */
const char *tail(const char *foo)
{
    const char *tmp = foo + strlen(foo);

    while (*tmp != '/' && tmp != foo)
	tmp--;

    if (*tmp == '/')
	tmp++;

    return tmp;
}

2384
#ifndef DISABLE_BROWSER
2385
/* Our sort routine for file listings -- sort directories before
2386
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2387
2388
int diralphasort(const void *va, const void *vb)
{
2389
2390
2391
2392
    struct stat fileinfo;
    const char *a = *(char *const *)va, *b = *(char *const *)vb;
    int aisdir = stat(a, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
    int bisdir = stat(b, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
Chris Allegretta's avatar
Chris Allegretta committed
2393

2394
    if (aisdir != 0 && bisdir == 0)
2395
	return -1;
2396
    if (aisdir == 0 && bisdir != 0)
2397
	return 1;
2398

2399
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2400
2401
}

2402
/* Free our malloc()ed memory */
2403
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2404
{
2405
2406
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2407
2408
2409
    free(array);
}

2410
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2411
2412
void striponedir(char *foo)
{
2413
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2414

2415
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2416
    /* Don't strip the root dir */
2417
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2418
2419
	return;

2420
2421
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2422
    if (*tmp == '/')
2423
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2424
2425
2426
2427
2428

    while (*tmp != '/' && tmp != foo)
	tmp--;

    if (tmp != foo)
2429
2430
2431
2432
2433
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2434
    }
Chris Allegretta's avatar
Chris Allegretta committed
2435
2436
}

2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
int readable_dir(const char *path)
{
    DIR *dir = opendir(path);

    /* If dir is NULL, don't do closedir(), since that changes errno. */
    if (dir != NULL)
	closedir(dir);
    return dir != NULL;
}

2447
/* Initialize the browser code, including the list of files in *path */
2448
char **browser_init(const char *path, int *longest, int *numents)
2449
2450
2451
{
    DIR *dir;
    struct dirent *next;
2452
    char **filelist;
2453
    int i = 0;
2454
    size_t path_len;
2455
2456

    dir = opendir(path);
2457
    if (dir == NULL)
2458
2459
2460
2461
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
2462
	if (strcmp(next->d_name, ".") == 0)
2463
2464
2465
2466
2467
2468
2469
2470
	   continue;
	(*numents)++;
	if (strlen(next->d_name) > *longest)
	    *longest = strlen(next->d_name);
    }
    rewinddir(dir);
    *longest += 10;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2471
    filelist = (char **)nmalloc(*numents * sizeof (char *));
2472

2473
    if (strcmp(path, "/") == 0)
2474
2475
2476
	path = "";
    path_len = strlen(path);

2477
    while ((next = readdir(dir)) != NULL) {
2478
	if (strcmp(next->d_name, ".") == 0)
2479
2480
	   continue;

2481
2482
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2483
2484
	i++;
    }
2485
    closedir(dir);
2486
2487
2488
2489
2490
2491
2492

    if (*longest > COLS - 1)
	*longest = COLS - 1;

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2493
/* Our browser function.  inpath is the path to start browsing from */
2494
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2495
2496
2497
2498
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2499
2500
2501
    int numents = 0, i = 0, j = 0, longest = 0, abort = 0, col = 0;
    int selected = 0, editline = 0, width = 0, filecols = 0, lineno = 0;
    int kbinput = ERR;
2502
    bool meta_key, func_key;
2503
    char **filelist = (char **)NULL;
2504
#ifndef DISABLE_MOUSE
2505
2506
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2507

2508
2509
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2510
2511
2512
    /* If path isn't the same as inpath, we are being passed a new
	dir as an arg.  We free it here so it will be copied from 
	inpath below */
2513
    if (path != NULL && strcmp(path, inpath) != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2514
2515
2516
2517
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2518
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2519
    if (path == NULL)
2520
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2521
2522

    filelist = browser_init(path, &longest, &numents);
2523
    foo = charalloc(longest + 8);
Chris Allegretta's avatar
Chris Allegretta committed
2524

2525
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2526
2527
2528
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2529
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2530
2531
2532
2533
2534
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2535
2536

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2537
    do {
2538
2539
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2540

2541
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2542

2543
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2544
	currshortcut = browser_list;
2545
2546
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2547
2548
 	editline = 0;
	col = 0;
2549
	    
2550
	/* Compute line number we're on now, so we don't divide by zero later */
2551
2552
2553
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2554
2555

	switch (kbinput) {
2556

2557
#ifndef DISABLE_MOUSE
2558
	case KEY_MOUSE:
2559
	    if (getmouse(&mevent) == ERR)
2560
		return retval;
2561
2562
2563
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2564
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2565
2566
2567
2568
		int selectedbackup = selected;

		mevent.y -= 2;

2569
2570
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2571
		selected = (lineno / editwinrows) * editwinrows * width
2572
2573
2574
2575
2576
2577
			+ mevent.y * width + mevent.x / (longest + 2);

		/* If they clicked beyond the end of a row, select the
		 * end of that row. */
		if (mevent.x > width * (longest + 2))
		    selected--;
2578
2579
2580

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2581
		if (selected > numents - 1)
2582
		    selected = numents - 1;
2583
		else if (selectedbackup == selected)
2584
		    unget_kbinput('s', FALSE);	/* Unget the 'select' key */
2585
2586
2587
2588
	    } else {	/* Must be clicking a shortcut */
		int mouse_x, mouse_y;
		get_mouseinput(&mouse_x, &mouse_y, TRUE);
	    }
2589

2590
2591
            break;
#endif
2592
	case NANO_PREVLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2593
2594
2595
	    if (selected - width >= 0)
		selected -= width;
	    break;
2596
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2597
2598
2599
	    if (selected > 0)
		selected--;
	    break;
2600
	case NANO_NEXTLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2601
2602
2603
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
2604
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2605
2606
2607
2608
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2609
	case NANO_PREVPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2610
	case '-': /* Pico compatibility */
2611
	    if (selected >= (editwinrows + lineno % editwinrows) * width)
2612
		selected -= (editwinrows + lineno % editwinrows) * width;
Chris Allegretta's avatar
Chris Allegretta committed
2613
2614
2615
2616
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2617
	case NANO_NEXTPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2618
	case ' ': /* Pico compatibility */
2619
2620
	    selected += (editwinrows - lineno % editwinrows) * width;
	    if (selected >= numents)
Chris Allegretta's avatar
Chris Allegretta committed
2621
2622
		selected = numents - 1;
	    break;
2623
2624
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2625
	case '?': /* Pico compatibility */
2626
#ifndef DISABLE_HELP
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2627
	    do_help();
2628
	    curs_set(0);
2629
2630
2631
#else
	    nano_disabled_msg();
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2632
	    break;
2633
	case NANO_ENTER_KEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2634
2635
	case 'S': /* Pico compatibility */
	case 's':
Chris Allegretta's avatar
Chris Allegretta committed
2636
	    /* You can't cd up from / */
2637
2638
	    if (strcmp(filelist[selected], "/..") == 0 &&
		strcmp(path, "/") == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2639
		statusbar(_("Can't move up a directory"));
2640
		beep();
Rocco Corsi's avatar
   
Rocco Corsi committed
2641
2642
2643
		break;
	    }

2644
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2645
2646
2647
	    /* Note: the selected file can be outside the operating
	     * directory if it is .. or if it is a symlink to 
	     * directory outside the operating directory. */
2648
	    if (check_operating_dir(filelist[selected], FALSE) != 0) {
2649
2650
		statusbar(_("Can't go outside of %s in restricted mode"),
			operating_dir);
2651
2652
		beep();
		break;
2653
2654
2655
	    }
#endif

2656
	    if (stat(filelist[selected], &st) == -1) {
2657
2658
		statusbar(_("Can't open \"%s\": %s"), filelist[selected],
			strerror(errno));
2659
2660
2661
		beep();
		break;
	    }
2662

2663
2664
2665
2666
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2667
2668
	    }

2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
	    new_path = mallocstrcpy(NULL, filelist[selected]);

	    if (strcmp("..", tail(new_path)) == 0) {
		/* They want to go up a level, so strip off .. and the
		   current dir */
		striponedir(new_path);
		/* SPK for '.' path, get the current path via getcwd */
		if (strcmp(new_path, ".") == 0) {
		    free(new_path);
		    new_path = getcwd(NULL, PATH_MAX + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2679
		}
2680
2681
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2682

2683
2684
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
2685
2686
		statusbar(_("Can't open \"%s\": %s"), new_path,
			strerror(errno));
2687
2688
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2689
	    }
2690
2691
2692
2693
2694
2695
2696

	    free_charptrarray(filelist, numents);
	    free(foo);
	    free(path);
	    path = new_path;
	    return do_browser(path);

2697
	/* Go to a specific directory */
2698
2699
	case NANO_GOTOLINE_KEY:
	case NANO_GOTOLINE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2700
2701
	case 'G': /* Pico compatibility */
	case 'g':
Rocco Corsi's avatar
   
Rocco Corsi committed
2702
	    curs_set(1);
2703
	    j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2704
#ifndef NANO_SMALL
2705
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2706
#endif
2707
		_("Go To Directory"));
2708
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2709
2710
2711
	    curs_set(0);

	    if (j < 0) {
2712
		statusbar(_("Cancelled"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2713
2714
2715
		break;
	    }

2716
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2717

2718
2719
2720
	    if (new_path[0] != '/') {
		new_path = charealloc(new_path, strlen(path) + strlen(answer) + 2);
		sprintf(new_path, "%s/%s", path, answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2721
2722
	    }

2723
#ifndef DISABLE_OPERATINGDIR
2724
	    if (check_operating_dir(new_path, FALSE) != 0) {
2725
2726
2727
2728
2729
2730
2731
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		free(new_path);
		break;
	    }
#endif

	    if (!readable_dir(new_path)) {
Rocco Corsi's avatar
   
Rocco Corsi committed
2732
2733
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2734
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2735
		break;
2736
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2737
2738

	    /* Start over again with the new path value */
2739
2740
	    free_charptrarray(filelist, numents);
	    free(foo);
2741
2742
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2743
2744
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2745
	/* Stuff we want to abort the browser */
2746
	case NANO_CANCEL_KEY:
2747
	case NANO_EXIT_KEY:
2748
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2749
2750
	case 'E': /* Pico compatibility */
	case 'e':
2751
2752
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2753
2754
2755
2756
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2757
2758
	blank_edit();

2759
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
	    i = width * editwinrows * ((selected / width) / editwinrows);
	else
	    i = 0;

	wmove(edit, 0, 0);
	for (j = i; j < numents && editline <= editwinrows - 1; j++) {
	    filecols++;

	    strncpy(foo, tail(filelist[j]), strlen(tail(filelist[j])) + 1);
	    while (strlen(foo) < longest)
		strcat(foo, " ");
	    col += strlen(foo);

	    /* Put file info in the string also */
2774
2775
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2776
2777
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2778
2779
2780
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2781
2782
2783
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
2784
		    stat(filelist[j], &st);
2785
2786
2787
2788
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2789
2790
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2791
			(int) st.st_size);
2792
2793
2794
2795
2796
2797
		else if (st.st_size >= (1 << 30)) /* at least 1 gig */
		    sprintf(foo + longest - 7, "%4d GB", 
			(int) st.st_size >> 30);
		else if (st.st_size >= (1 << 20)) /* at least 1 meg */
		    sprintf(foo + longest - 7, "%4d MB", 
			(int) st.st_size >>     20);
2798
		else /* It's more than 1 k and less than a meg */
2799
2800
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2801
2802
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2803
	    /* Highlight the currently selected file/dir */
2804
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2805
		wattron(edit, A_REVERSE);
2806
2807
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2808
2809
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2810
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2811
2812
2813
2814
2815
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2816
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2817
2818
2819
2820
2821
2822
2823
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
2824
	wrefresh(edit);
2825
2826
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
Chris Allegretta's avatar
Chris Allegretta committed
2827
2828
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2829
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2830
2831
    edit_refresh();

Chris Allegretta's avatar
Chris Allegretta committed
2832
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2833
2834
2835
2836
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2837

2838
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2839
 starts do_browser from there, else from the current dir */
2840
char *do_browse_from(const char *inpath)
2841
2842
{
    struct stat st;
2843
    char *bob;
2844
2845
2846
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2847

2848
2849
2850
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2851

2852
2853
2854
2855
2856
2857
2858
2859
    /*
     * Perhaps path is a directory.  If so, we will pass that to
     * do_browser.  Otherwise, perhaps path is a directory / a file.  So
     * we try stripping off the last path element.  If it still isn't a
     * directory, just use the current directory. */

    if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	striponedir(path);
2860
2861
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2862
	    path = getcwd(NULL, PATH_MAX + 1);
2863
	}
2864
    }
2865

2866
2867
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2868
    if (check_operating_dir(path, FALSE) != 0)
2869
2870
2871
2872
2873
2874
2875
2876
2877
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2878
    return bob;
2879
}
Chris Allegretta's avatar
Chris Allegretta committed
2880
#endif /* !DISABLE_BROWSER */
2881

2882
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2883
2884
2885
/* Return $HOME/.nano_history, or NULL if we can't find the homedir.
 * The string is dynamically allocated, and should be freed. */
char *histfilename(void)
2886
{
2887
    char *nanohist = NULL;
2888

2889
2890
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2891

2892
2893
2894
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2895
    }
2896
2897
2898
2899
2900
2901
    return nanohist;
}

void load_history(void)
{
    char *nanohist = histfilename();
Chris Allegretta's avatar
Chris Allegretta committed
2902

2903
    /* assume do_rcfile() has reported missing home dir */
2904
2905
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2906

2907
	if (hist == NULL) {
2908
	    if (errno != ENOENT) {
2909
2910
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2911
		rcfile_error(N_("Error reading %s: %s"), nanohist, strerror(errno));
2912
2913
2914
		fprintf(stderr, _("\nPress Return to continue starting nano\n"));
		while (getchar() != '\n')
		    ;
2915
	    }
2916
	} else {
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
	    historyheadtype *history = &search_history;
	    char *line = NULL;
	    size_t buflen = 0;
	    ssize_t read;

	    while ((read = getline(&line, &buflen, hist)) >= 0) {
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
		    update_history(history, line);
		} else
2931
2932
2933
		    history = &replace_history;
	    }
	    fclose(hist);
2934
	    free(line);
2935
2936
	    UNSET(HISTORY_CHANGED);
	}
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
	free(nanohist);
    }
}

bool writehist(FILE *hist, historyheadtype *histhead)
{
    historytype *h;

    /* write oldest first */
    for (h = histhead->tail; h->prev != NULL; h = h->prev) {
	size_t len = strlen(h->data);

	sunder(h->data);
	if (fwrite(h->data, sizeof(char), len, hist) < len ||
		putc('\n', hist) == EOF)
	    return FALSE;
2953
    }
2954
    return TRUE;
2955
2956
2957
2958
2959
}

/* save histories to ~/.nano_history */
void save_history(void)
{
2960
    char *nanohist;
2961
2962

    /* don't save unchanged or empty histories */
2963
    if ((search_history.count == 0 && replace_history.count == 0) ||
2964
	!ISSET(HISTORY_CHANGED) || ISSET(VIEW_MODE))
2965
2966
	return;

2967
2968
2969
2970
    nanohist = histfilename();

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

2972
	if (hist == NULL)
2973
	    rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2974
	else {
2975
2976
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2977
2978
2979
2980

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2981
		rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2982
2983
2984
2985
2986
	    fclose(hist);
	}
	free(nanohist);
    }
}
2987
#endif /* !NANO_SMALL && ENABLE_NANORC */