files.c 80.3 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>
29
#include <utime.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
31
#include <fcntl.h>
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
32
33
#include <ctype.h>
#include <dirent.h>
34
#include <pwd.h>
Chris Allegretta's avatar
Chris Allegretta committed
35
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
36
37
#include "proto.h"
#include "nano.h"
Chris Allegretta's avatar
Chris Allegretta committed
38

39
40
static file_format fmt = NIX_FILE;
	/* The format of the current file. */
41

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

55
56
#ifdef ENABLE_COLOR
    update_color();
57
58
    if (ISSET(COLOR_SYNTAX))
	edit_refresh();
59
#endif
Chris Allegretta's avatar
Chris Allegretta committed
60
61
}

62
63
64
65
66
67
/* 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
68
{
69
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
70

71
72
    /* Convert nulls to newlines.  len is the string's real length
     * here. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
73
74
    unsunder(buf, len);

75
76
77
    assert(strlen(buf) == len);

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

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

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

    return fileptr;
}

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* 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
130
{
131
132
133
134
135
136
137
138
139
140
    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
141
    char *buf;
142
143
144
145
146
147
148
149
	/* 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. */
150
#ifndef NANO_SMALL
151
    int format = 0;
152
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
153
#endif
Chris Allegretta's avatar
Chris Allegretta committed
154

155
    buf = charalloc(bufx);
156
    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
157

158
159
    if (current != NULL) {
	if (current == fileage)
160
	    first_line_ins = TRUE;
161
162
	else
	    fileptr = current->prev;
Chris Allegretta's avatar
Chris Allegretta committed
163
    }
164

165
166
    /* For the assertion in read_line(), it must be true that if current
     * is NULL, then so is fileage. */
167
168
    assert(current != NULL || fileage == NULL);

169
#ifndef NANO_SMALL
170
171
172
    /* We don't know which file format we have yet, so assume it's a
     * *nix file for now. */
    fmt = NIX_FILE;
173
174
#endif

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

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

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

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

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

199
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
200
	    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
201
	    i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
202
#ifndef NANO_SMALL
203
204
	/* 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
205
206
	} else if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r') {

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

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

216
217
218
	    /* 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. */
219
	    len = 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
220

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

Chris Allegretta's avatar
Chris Allegretta committed
232
	    /* Now we allocate a bigger buffer 128 characters at a time.
233
234
235
	     * 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
236
237
	    if (i >= bufx - 1) {
		bufx += 128;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
238
		buf = charealloc(buf, bufx);
Chris Allegretta's avatar
Chris Allegretta committed
239
	    }
240
	    buf[i] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
241
	    buf[i + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
242
243
	    i++;
	}
244
245
246
	totsize++;
    }

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

253
#ifndef NANO_SMALL
254
255
256
    /* 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') {
257
258
259
	buf[0] = input;
	buf[1] = '\0';
	len = 1;
260
261
    }
#endif
262

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

275
276
277
278
279
280
281
282
283
	/* 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. */
284
    if (totsize == 0 || fileptr == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
285
	new_file();
Robert Siemborski's avatar
Robert Siemborski committed
286

287
    /* Did we try to insert a file of 0 bytes? */
288
289
290
291
292
293
294
295
296
297
298
299
    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
300
    }
301
302

#ifndef NANO_SMALL
303
    if (format == 3)
304
305
306
307
	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);
308
309
    else if (format == 2) {
	fmt = MAC_FILE;
310
311
312
	statusbar(P_("Read %lu line (Converted from Mac format)",
		"Read %lu lines (Converted from Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
313
314
    } else if (format == 1) {
	fmt = DOS_FILE;
315
316
317
	statusbar(P_("Read %lu line (Converted from DOS format)",
		"Read %lu lines (Converted from DOS format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
318
    } else
319
#endif
320
	statusbar(P_("Read %lu line", "Read %lu lines",
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
321
		(unsigned long)num_lines),(unsigned long)num_lines);
322

323
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
324
325
}

326
327
328
329
330
331
332
333
/* 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
334
335
336
337
{
    int fd;
    struct stat fileinfo;

338
339
340
341
    assert(f != NULL);
    if (filename == NULL || filename[0] == '\0' ||
	    stat(filename, &fileinfo) == -1) {
	if (newfie) {
Chris Allegretta's avatar
Chris Allegretta committed
342
	    statusbar(_("New File"));
343
	    return -2;
Chris Allegretta's avatar
Chris Allegretta committed
344
	}
345
346
	statusbar(_("\"%s\" not found"), filename);
	return -1;
347
348
349
350
351
    } 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);
352
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
353
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
354
355
356
357
358
359
360
361
	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));
362
	    close(fd);
363
364
	} else
	    statusbar(_("Reading File"));
Chris Allegretta's avatar
Chris Allegretta committed
365
    }
366
    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
367
368
}

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

379
    buf = charalloc(namelen + num_of_digits(INT_MAX) + 7);
380
    strcpy(buf, name);
381
382
    strcpy(buf + namelen, ".save");
    namelen += 5;
383

384
    while (TRUE) {
385
	struct stat fs;
386
387

	if (stat(buf, &fs) == -1)
Chris Allegretta's avatar
Chris Allegretta committed
388
	    return buf;
389
390
391
392
	if (i == INT_MAX)
	    break;

	i++;
393
	sprintf(buf + namelen, ".%d", i);
394
395
    }

Chris Allegretta's avatar
Chris Allegretta committed
396
    /* We get here only if there is no possible save file. */
397
    null_at(&buf, 0);
398
399
400
    return buf;
}

401
402
#ifndef NANO_SMALL
void execute_command(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
403
{
404
405
406
407
408
409
410
#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);
411
    }
412
413
414
415
416
417
418
419
420
#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
421

422
423
424
425
/* 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)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
426
    bool new_buffer = (fileage == NULL
427
428
#ifdef ENABLE_MULTIBUFFER
	 || ISSET(MULTIBUFFER)
429
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
430
	);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
431
432
433
434
	/* new_buffer says whether we load into 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. */
435
436
437
438
    FILE *f;
    int rc;
	/* rc == -2 means that the statusbar displayed "New File".  -1
	 * means that the open failed.  0 means success. */
439

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

447
448
449
#ifdef ENABLE_MULTIBUFFER
    /* Update the current entry in the open_files structure. */
    add_open_file(TRUE);
450
451
#endif

452
453
454
455
456
    rc = open_file(name, new_buffer, &f);

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

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

    if (rc == 0) {
469
470
	file_format fmt_save = fmt;

471
	read_file(f, filename);
472
473
474
475
476
477

	/* If we're not loading into a new buffer, preserve the file
	 * format. */
	if (!new_buffer)
	    fmt = fmt_save;

Chris Allegretta's avatar
Chris Allegretta committed
478
#ifndef NANO_SMALL
479
	stat(filename, &originalfilestat);
Chris Allegretta's avatar
Chris Allegretta committed
480
#endif
481
482
483
484
485
486
487
488
    }

    /* 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();
}

489
490
491
492
493
494
495
void do_insertfile(
#ifndef NANO_SMALL
	bool execute
#else
	void
#endif
	)
496
497
498
{
    int i;
    const char *msg;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
499
    char *ans = mallocstrcpy(NULL, "");
500
	/* The last answer the user typed on the statusbar. */
501
    filestruct *edittop_save = edittop;
502

503
#ifndef DISABLE_WRAPPING
504
    wrap_reset();
505
#endif
506

507
    while (TRUE) {
508
#ifndef NANO_SMALL
509
	if (execute) {
510
#ifdef ENABLE_MULTIBUFFER
511
512
513
	    if (ISSET(MULTIBUFFER))
		msg = N_("Command to execute in new buffer [from %s] ");
	    else
514
#endif
515
516
		msg = N_("Command to execute [from %s] ");
	} else {
517
518
#endif
#ifdef ENABLE_MULTIBUFFER
519
520
521
	    if (ISSET(MULTIBUFFER)) {
		msg = N_("File to insert into new buffer [from %s] ");
	    } else
522
#endif
523
		msg = N_("File to insert [from %s] ");
524
525
#ifndef NANO_SMALL
	}
526
#endif
527

528
	i = statusq(TRUE,
529
#ifndef NANO_SMALL
530
		execute ? extcmd_list :
531
532
#endif
		insertfile_list, ans,
Chris Allegretta's avatar
Chris Allegretta committed
533
#ifndef NANO_SMALL
534
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
535
#endif
536
537
538
539
		_(msg),
#ifndef DISABLE_OPERATINGDIR
		operating_dir != NULL && strcmp(operating_dir, ".") != 0 ?
		operating_dir :
540
#endif
541
		"./");
542

543
544
545
546
	if (i < 0) {
	    statusbar(_("Cancelled"));
	    break;
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
547
	    size_t pww_save = placewewant;
548

549
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
550

551
#if !defined(NANO_SMALL) && defined(ENABLE_MULTIBUFFER)
552
553
554
555
556
557
	    if (i == TOGGLE_MULTIBUFFER_KEY) {
		/* Don't allow toggling if we're in view mode. */
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
		continue;
	    }
558
#endif
559

560
#ifndef DISABLE_BROWSER
561
562
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
563

564
565
		if (tmp == NULL)
		    continue;
566
567
		free(answer);
		answer = tmp;
568
569
570
571

		/* We have a file now.  Get out of the statusbar prompt
		 * cleanly. */
		statusq_abort();
572
	    }
Chris Allegretta's avatar
Chris Allegretta committed
573
574
#endif

575
#ifndef NANO_SMALL
576
577
578
579
	    if (i == NANO_TOOTHERINSERT_KEY) {
		execute = !execute;
		continue;
	    }
580
#endif
581
582

#ifdef ENABLE_MULTIBUFFER
583
	    if (!ISSET(MULTIBUFFER)) {
584
#endif
585
586
587
588
589
		/* If we're not inserting into a new buffer, partition
		 * the filestruct so that it contains no text and hence
		 * looks like a new buffer, and set edittop to the top
		 * of the partition. */
		filepart = partition_filestruct(current, current_x,
590
			current, current_x);
591
		edittop = fileage;
592
#ifdef ENABLE_MULTIBUFFER
593
	    }
594
595
#endif

596
597
598
599
600
601
#ifndef NANO_SMALL
	    if (execute)
		execute_command(answer);
	    else {
#endif
		answer = mallocstrassn(answer, real_dir_from_tilde(answer));
602
		load_buffer(answer);
603
604
605
#ifndef NANO_SMALL
	    }
#endif
606
607

#ifdef ENABLE_MULTIBUFFER
608
	    if (!ISSET(MULTIBUFFER))
609
#endif
610
611
	    {
		filestruct *top_save = fileage;
612

613
614
615
616
617
618
		/* If we're not inserting into a new buffer, unpartition
		 * the filestruct so that it contains all the text
		 * again.  Note that we've replaced the non-text
		 * originally in the partition with the text in the
		 * inserted file/executed command output. */
		unpartition_filestruct(filepart);
619

620
621
622
		/* Renumber starting with the beginning line of the old
		 * partition. */
		renumber(top_save);
623

624
625
		/* Set edittop back to what it was before. */
		edittop = edittop_save;
626
	    }
627

628
#ifdef ENABLE_MULTIBUFFER
629
630
631
	    if (ISSET(MULTIBUFFER)) {
		/* Update the titlebar. */
		titlebar(NULL);
632

633
634
635
		/* Reinitialize the shortcut list. */
		shortcut_init(FALSE);
	    } else {
636
#endif
637
638
		/* Mark the file as modified. */
		set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
639

640
		/* Restore the old place we want. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
641
		placewewant = pww_save;
642
#ifdef ENABLE_MULTIBUFFER
643
	    }
Chris Allegretta's avatar
Chris Allegretta committed
644
645
#endif

646
647
648
649
650
651
	    /* Refresh the screen. */
	    edit_refresh();

	    break;
	}
    } /* while (TRUE) */
Chris Allegretta's avatar
Chris Allegretta committed
652

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
653
    free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
654
655
}

656
void do_insertfile_void(void)
657
{
658
#ifdef ENABLE_MULTIBUFFER
659
660
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
	statusbar(_("Key illegal in non-multibuffer mode"));
661
    else
662
#endif
663
664
665
666
667
	do_insertfile(
#ifndef NANO_SMALL
		FALSE
#endif
		);
668
669
670
671

    display_main_list();
}

672
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
673
674
675
/* Create a new openfilestruct node. */
openfilestruct *make_new_opennode(openfilestruct *prevnode)
{
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
676
    openfilestruct *newnode = (openfilestruct *)nmalloc(sizeof(openfilestruct));
Chris Allegretta's avatar
Chris Allegretta committed
677
678
679
680
681
682
683
684
685
686
687
688
689

    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,
690
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
{
    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
732
	    fprintf(stderr, "%s: free'd a node, YAY!\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
733
734
735
736
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
737
	fprintf(stderr, "%s: free'd last node.\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
738
739
740
741
#endif
    }
}

742
/*
743
 * Add/update an entry to the open_files openfilestruct.  If update is
744
745
 * FALSE, a new entry is created; otherwise, the current entry is
 * updated.
746
 */
747
void add_open_file(bool update)
748
{
749
    openfilestruct *tmp;
750

751
    if (fileage == NULL || current == NULL || filename == NULL)
752
	return;
753
754

    /* if no entries, make the first one */
755
    if (open_files == NULL)
756
	open_files = make_new_opennode(NULL);
757
758
759
760
761
762
763

    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
764
	fprintf(stderr, "filename is %s\n", open_files->filename);
765
766
#endif

767
768
	tmp = make_new_opennode(NULL);
	splice_opennode(open_files, tmp, open_files->next);
769
770
771
772
	open_files = open_files->next;
    }

    /* save current filename */
773
    open_files->filename = mallocstrcpy(open_files->filename, filename);
774

775
776
777
778
779
#ifndef NANO_SMALL
    /* save the file's stat */
    open_files->originalfilestat = originalfilestat;
#endif

780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
    /* 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 */
796
    open_files->file_lineno = current->lineno;
797

798
799
800
    /* start with default modification status: unmodified, unmarked (if
       available), not in DOS format (if available), and not in Mac
       format (if available) */
801
802
    open_files->file_flags = 0;

803
    /* if we're updating, save current modification status, current
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
804
       marking status (if available), and current file format (if
805
       available) */
Chris Allegretta's avatar
Chris Allegretta committed
806
    if (update) {
807
808
	if (ISSET(MODIFIED))
	    open_files->file_flags |= MODIFIED;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
809
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
810
811
812
	if (ISSET(MARK_ISSET)) {
	    open_files->file_mark_beginbuf = mark_beginbuf;
	    open_files->file_mark_beginx = mark_beginx;
813
	    open_files->file_flags |= MARK_ISSET;
Chris Allegretta's avatar
Chris Allegretta committed
814
	}
815
	open_files->file_fmt = fmt;
Chris Allegretta's avatar
Chris Allegretta committed
816
817
818
#endif
    }

819
820
821
    /* 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
822
823
824
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
825
    }
826
827

#ifdef DEBUG
828
    fprintf(stderr, "filename is %s\n", open_files->filename);
829
830
831
832
833
#endif
}

/*
 * Read the current entry in the open_files structure and set up the
834
 * currently open file using that entry's information.
835
 */
836
void load_open_file(void)
837
{
838
    if (open_files == NULL)
839
	return;
840
841
842

    /* set up the filename, the file buffer, the total number of lines in
       the file, and the total file size */
843
    filename = mallocstrcpy(filename, open_files->filename);
844
845
846
#ifndef NANO_SMALL
    originalfilestat = open_files->originalfilestat;
#endif
847
    fileage = open_files->fileage;
848
    current = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
849
    filebot = open_files->filebot;
850
851
852
    totlines = open_files->file_totlines;
    totsize = open_files->file_totsize;

Chris Allegretta's avatar
Chris Allegretta committed
853
854
855
856
857
858
859
860
861
862
863
864
865
866
    /* 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);
867

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
868
    /* restore file format */
869
    fmt = open_files->file_fmt;
Chris Allegretta's avatar
Chris Allegretta committed
870
#endif
871

Chris Allegretta's avatar
Chris Allegretta committed
872
873
874
875
#ifdef ENABLE_COLOR
    update_color();
#endif

876
877
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
878
879
    do_gotopos(open_files->file_lineno, open_files->file_current_x,
	open_files->file_current_y, open_files->file_placewewant);
880

Chris Allegretta's avatar
Chris Allegretta committed
881
    /* update the titlebar */
882
883
884
885
886
887
    clearok(topwin, FALSE);
    titlebar(NULL);
}

/*
 * Open the previous entry in the open_files structure.  If closing_file
888
889
890
 * is FALSE, update the current entry before switching from it.
 * Otherwise, we are about to close that entry, so don't bother doing
 * so.
891
 */
892
void open_prevfile(bool closing_file)
893
{
894
    if (open_files == NULL)
895
	return;
896
897

    /* if we're not about to close the current entry, update it before
898
       doing anything */
899
    if (!closing_file)
900
	add_open_file(TRUE);
901

902
    if (open_files->prev == NULL && open_files->next == NULL) {
903
904
905

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
906
	    statusbar(_("No more open file buffers"));
907
	return;
908
909
    }

910
    if (open_files->prev != NULL) {
911
912
913
	open_files = open_files->prev;

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

    }

919
    else if (open_files->next != NULL) {
920
921

	/* if we're at the beginning, wrap around to the end */
922
	while (open_files->next != NULL)
923
924
925
	    open_files = open_files->next;

#ifdef DEBUG
926
	    fprintf(stderr, "filename is %s\n", open_files->filename);
927
928
929
930
931
932
#endif

    }

    load_open_file();

933
    statusbar(_("Switched to %s"),
934
      ((open_files->filename[0] == '\0') ? _("New Buffer") :
935
	open_files->filename));
936

937
938
939
940
941
#ifdef DEBUG
    dump_buffer(current);
#endif
}

942
void open_prevfile_void(void)
943
{
944
    open_prevfile(FALSE);
945
946
}

947
948
/*
 * Open the next entry in the open_files structure.  If closing_file is
949
950
 * FALSE, update the current entry before switching from it.  Otherwise,
 * we are about to close that entry, so don't bother doing so.
951
 */
952
void open_nextfile(bool closing_file)
953
{
954
    if (open_files == NULL)
955
	return;
956
957

    /* if we're not about to close the current entry, update it before
958
       doing anything */
959
    if (!closing_file)
960
	add_open_file(TRUE);
961

962
    if (open_files->prev == NULL && open_files->next == NULL) {
963
964
965

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
966
	    statusbar(_("No more open file buffers"));
967
	return;
968
969
    }

970
    if (open_files->next != NULL) {
971
972
973
	open_files = open_files->next;

#ifdef DEBUG
974
	fprintf(stderr, "filename is %s\n", open_files->filename);
975
976
977
#endif

    }
978
    else if (open_files->prev != NULL) {
979
980

	/* if we're at the end, wrap around to the beginning */
981
	while (open_files->prev != NULL) {
982
983
984
	    open_files = open_files->prev;

#ifdef DEBUG
985
	    fprintf(stderr, "filename is %s\n", open_files->filename);
986
987
988
989
990
991
992
#endif

	}
    }

    load_open_file();

993
    statusbar(_("Switched to %s"),
994
      ((open_files->filename[0] == '\0') ? _("New Buffer") :
995
	open_files->filename));
996

997
998
999
1000
1001
#ifdef DEBUG
    dump_buffer(current);
#endif
}

1002
void open_nextfile_void(void)
1003
{
1004
    open_nextfile(FALSE);
1005
1006
}

1007
1008
/*
 * Delete an entry from the open_files filestruct.  After deletion of an
1009
 * entry, the next or previous entry is opened, whichever is found first.
1010
 * Return TRUE on success or FALSE on error.
1011
 */
1012
bool close_open_file(void)
1013
{
1014
    openfilestruct *tmp;
1015

1016
    if (open_files == NULL)
1017
	return FALSE;
1018

Chris Allegretta's avatar
Chris Allegretta committed
1019
1020
1021
    /* 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 */
1022
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
1023
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1024

1025
    tmp = open_files;
1026
1027
1028
1029
1030
    if (open_files->next != NULL)
	open_nextfile(TRUE);
    else if (open_files->prev != NULL)
	open_prevfile(TRUE);
    else
1031
	return FALSE;
1032

1033
1034
    unlink_opennode(tmp);
    delete_opennode(tmp);
1035

1036
    shortcut_init(FALSE);
1037
    display_main_list();
1038
    return TRUE;
1039
}
1040
#endif /* ENABLE_MULTIBUFFER */
1041

1042
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR) || !defined(NANO_SMALL)
1043
/*
1044
1045
1046
1047
1048
1049
 * 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).
1050
 */
1051
char *get_full_path(const char *origpath)
1052
{
1053
1054
1055
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
1056
    char *expanded_origpath;
1057

1058
    /* first, get the current directory, and tack a slash onto the end of
1059
       it, unless it turns out to be "/", in which case leave it alone */
1060
1061
1062

    d_here = getcwd(NULL, PATH_MAX + 1);

1063
    if (d_here != NULL) {
1064
1065

	align(&d_here);
1066
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1067
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1068
1069
	    strcat(d_here, "/");
	}
1070
1071
1072
1073
1074

	/* 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 */
1075
	path_only = !stat(origpath, &fileinfo) && S_ISDIR(fileinfo.st_mode);
1076

1077
	expanded_origpath = real_dir_from_tilde(origpath);
1078
	/* save the value of origpath in both d_there and d_there_file */
1079
1080
1081
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1082

1083
1084
1085
1086
1087
1088
	/* 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
1089
		d_there = charealloc(d_there, strlen(d_there) + 2);
1090
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1091
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1092
1093
1094
1095
		strcat(d_there_file, "/");
	    }
	}

1096
1097
1098
1099
1100
	/* 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 */
1101
	if (last_slash == NULL)
1102
	    d_there = mallocstrcpy(d_there, d_here);
1103
	else {
1104
1105
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1106
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1107
	    null_at(&d_there, last_slash_index + 1);
1108
1109
1110

	    /* and remove all non-file elements from d_there_file (i. e.
	       everything before and including the last slash); if we
1111
	       have a path but no filename, don't do anything */
1112
1113
1114
1115
1116
1117
	    if (!path_only) {
		last_slash = strrchr(d_there_file, '/');
		last_slash++;
		strcpy(d_there_file, last_slash);
		align(&d_there_file);
	    }
1118
1119
1120

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1121
1122
1123
		/* 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 */
1124
1125
1126
1127
1128
1129

		free(d_there);

		d_there = getcwd(NULL, PATH_MAX + 1);

		align(&d_there);
1130
		if (d_there != NULL) {
1131
1132
1133

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
1134
		    if (strcmp(d_there, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1135
			d_there = charealloc(d_there, strlen(d_there) + 2);
1136
1137
			strcat(d_there, "/");
		    }
1138
1139
1140
		}
		else
		    return NULL;
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
	    }

	    /* 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 */

1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
	/* 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);
	}
1162
1163
1164
1165
1166
1167
1168
1169
1170

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

    return newpath;
}
1171
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1172
1173
1174

#ifndef DISABLE_SPELLER
/*
1175
1176
1177
 * 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.
1178
 */
1179
char *check_writable_directory(const char *path)
1180
{
1181
    char *full_path = get_full_path(path);
1182
    int writable;
1183
1184
    struct stat fileinfo;

1185
    /* if get_full_path() failed, return NULL */
1186
    if (full_path == NULL)
1187
	return NULL;
1188
1189
1190

    /* 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 */
1191
    writable = !stat(full_path, &fileinfo) && (fileinfo.st_mode & S_IWUSR);
1192
1193

    /* if the full path doesn't end in a slash (meaning get_full_path()
1194
1195
1196
1197
       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);
1198
	return NULL;
1199
    }
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209

    /* 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
1210
1211
1212
1213
1214
1215
1216
 * 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.
1217
 */
1218
1219
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1220
1221
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1222
    int filedesc;
1223

1224
1225
      /* 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,
1226
         leave full_tempdir set to NULL */
1227
    TMPDIR_env = getenv("TMPDIR");
1228
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1229
	full_tempdir = check_writable_directory(TMPDIR_env);
1230

1231
1232
1233
    /* 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 */
1234
    if (full_tempdir == NULL && dirname != NULL)
1235
	full_tempdir = check_writable_directory(dirname);
1236
1237
1238

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1239
    if (full_tempdir == NULL)
1240
	full_tempdir = check_writable_directory(P_tmpdir);
1241
1242

    /* if P_tmpdir didn't work, use /tmp instead */
1243
    if (full_tempdir == NULL) {
1244
1245
1246
1247
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1248
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1249
1250

    /* like tempnam(), use only the first 5 characters of the prefix */
1251
1252
1253
1254
1255
1256
1257
1258
    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) {
1259
	close(filedesc);
1260
1261
	unlink(full_tempdir);
	return full_tempdir;
1262
    }
1263
1264
1265

    free(full_tempdir);
    return NULL;
1266
1267
}
#endif /* !DISABLE_SPELLER */
1268
1269

#ifndef DISABLE_OPERATINGDIR
1270
1271
1272
1273
1274
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1275
    if (operating_dir == NULL)
1276
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1277

1278
1279
1280
    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
1281
     * inaccessible, unset operating_dir. */
1282
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1283
1284
1285
1286
1287
1288
1289
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1290
/* Check to see if we're inside the operating directory.  Return 0 if we
1291
1292
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1293
 * completion will work. */
1294
int check_operating_dir(const char *currpath, int allow_tabcomp)
1295
{
1296
1297
1298
1299
    /* 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. */
1300

1301
1302
1303
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1304

1305
    /* If no operating directory is set, don't bother doing anything. */
1306
    if (operating_dir == NULL)
1307
	return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1308

1309
    assert(full_operating_dir != NULL);
1310
1311

    fullpath = get_full_path(currpath);
1312
1313
1314
1315
1316
1317
1318
1319

    /* 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. */
1320
    if (fullpath == NULL)
1321
	return allow_tabcomp;
1322
1323
1324
1325
1326

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

1327
1328
1329
1330
1331
    /* 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. */
1332
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1333
1334
	retval = 1;
    free(fullpath);	
1335
1336

    /* Otherwise, we're still inside it. */
1337
    return retval;
1338
}
1339
1340
#endif

1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
#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

1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
/* 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;
}

1393
/* Write a file out.  If tmp is FALSE, we set the umask to disallow
1394
1395
1396
 * 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
1397
 *
1398
1399
 * 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.
1400
 *
1401
1402
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1403
 *
1404
1405
 * nonamechange means don't change the current filename.  It is ignored
 * if tmp is FALSE or if we're appending/prepending.
1406
1407
 *
 * Return -1 on error, 1 on success. */
1408
1409
int write_file(const char *name, bool tmp, int append, bool
	nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1410
{
1411
1412
    int retval = -1;
	/* Instead of returning in this function, you should always
1413
1414
1415
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
    const filestruct *fileptr = fileage;
1416
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1417
	/* The file descriptor we use. */
1418
    mode_t original_umask = 0;
1419
	/* Our umask, from when nano started. */
1420
    bool realexists;
1421
	/* The result of stat().  TRUE if the file exists, FALSE
1422
	 * otherwise.  If name is a link that points nowhere, realexists
1423
	 * is FALSE. */
1424
1425
    struct stat st;
	/* The status fields filled in by stat(). */
1426
    bool anyexists;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1427
	/* The result of lstat().  Same as realexists unless name is a
1428
1429
1430
1431
	 * link. */
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1432
	/* name after tilde expansion. */
1433
1434
1435
1436
    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
1437

1438
    assert(name != NULL);
1439
    if (name[0] == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1440
	return -1;
1441
1442
    if (!tmp)
	titlebar(NULL);
1443

1444
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1445

1446
#ifndef DISABLE_OPERATINGDIR
1447
    /* If we're writing a temporary file, we're probably going outside
1448
     * the operating directory, so skip the operating directory test. */
1449
    if (!tmp && check_operating_dir(realname, FALSE) != 0) {
1450
1451
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1452
1453
1454
    }
#endif

1455
    anyexists = (lstat(realname, &lst) != -1);
1456
1457
1458
1459
1460
1461
    /* 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)) {
1462
1463
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1464
1465
1466
	goto cleanup_and_exit;
    }

1467
    /* Save the state of file at the end of the symlink (if there is
1468
     * one). */
1469
    realexists = (stat(realname, &st) != -1);
1470

1471
1472
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1473
1474
1475
1476
     * 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. */
1477
    if (ISSET(BACKUP_FILE) && !tmp && realexists &&
1478
1479
1480
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1481
	FILE *backup_file;
1482
	char *backupname;
1483
	struct utimbuf filetime;
1484
	int copy_status;
1485

1486
	/* Save the original file's access and modification times. */
1487
1488
1489
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1490
	/* Open the original file to copy to the backup. */
1491
	f = fopen(realname, "rb");
1492
	if (f == NULL) {
1493
	    statusbar(_("Error reading %s: %s"), realname,
1494
		strerror(errno));
1495
	    goto cleanup_and_exit;
1496
1497
	}

1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
	/* 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);
	}
1530

1531
1532
1533
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1534
	backup_file = fopen(backupname, "wb");
1535
1536
1537
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
	    statusbar(_("Error writing %s: %s"), backupname, strerror(errno));
1538
	    free(backupname);
1539
1540
1541
1542
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1543
1544
1545
	}

#ifdef DEBUG
1546
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1547
1548
#endif

1549
1550
1551
1552
1553
1554
1555
1556
	/* 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)
1557
		statusbar(_("Error reading %s: %s"), realname, strerror(errno));
1558
1559
1560
1561
1562
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1563
1564
	free(backupname);
    }
1565
#endif /* !NANO_SMALL */
1566

1567
1568
1569
1570
1571
1572
    /* 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));
1573
	goto cleanup_and_exit;
1574
    }
1575

1576
1577
    original_umask = umask(0);
    umask(original_umask);
1578

1579
    /* If we create a temp file, we don't let anyone else access it.  We
1580
     * create a temp file if tmp is TRUE or if we're prepending. */
1581
1582
1583
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

1584
    /* If we're prepending, copy the file to a temp file. */
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
    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);
1602
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1603
	}
1604

1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
	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);
1621
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1622
1623
1624
	}
    }

1625
1626
    /* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This is
     * now copied from joe, because wiggy says so *shrug*. */
1627
1628
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
1629
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1630

1631
    /* Set the umask back to the user's original value. */
1632
1633
1634
1635
1636
    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));
1637
1638
1639
	/* tempname has been set only if we're prepending. */
	if (tempname != NULL)
	    unlink(tempname);
1640
1641
	goto cleanup_and_exit;
    }
1642

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1643
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1644
    if (f == NULL) {
1645
1646
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1647
	goto cleanup_and_exit;
1648
1649
    }

1650
    /* There might not be a magicline.  There won't be when writing out
1651
1652
1653
1654
1655
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1656

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

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

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

1665
	if (size < data_len) {
1666
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1667
	    fclose(f);
1668
	    goto cleanup_and_exit;
1669
	}
1670
#ifndef NANO_SMALL
1671
	if (fmt == DOS_FILE || fmt == MAC_FILE)
1672
1673
1674
1675
1676
	    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
1677

1678
	if (fmt != MAC_FILE)
1679
#endif
1680
1681
1682
1683
1684
	    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
1685
1686
1687
1688
1689

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

1690
    /* If we're prepending, open the temp file, and append it to f. */
1691
    if (append == 2) {
1692
1693
1694
1695
1696
1697
1698
1699
	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);
1700
	}
1701
	if (f_source == NULL) {
1702
1703
	    statusbar(_("Error reading %s: %s"), tempname, strerror(errno));
	    fclose(f);
1704
	    goto cleanup_and_exit;
1705
1706
	}

1707
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
1708
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1709
	    goto cleanup_and_exit;
1710
	}
1711
1712
1713
1714
    } 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
1715
    }
1716

1717
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1718
	if (!nonamechange) {
1719
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1720
1721
#ifdef ENABLE_COLOR
	    update_color();
1722
1723
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1724
1725
#endif
	}
1726

1727
1728
1729
1730
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1731
1732
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1733
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1734
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1735
    }
1736
1737
1738
1739
1740

    retval = 1;

  cleanup_and_exit:
    free(realname);
1741
    free(tempname);
1742
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1743
1744
}

1745
1746
1747
#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
1748
1749
1750
1751
 * 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. */
1752
int write_marked(const char *name, bool tmp, int append)
1753
1754
{
    int retval = -1;
1755
    bool old_modified = ISSET(MODIFIED);
1756
	/* write_file() unsets the MODIFIED flag. */
1757
1758
1759
1760
1761
1762
1763
1764
    bool added_magicline;
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

    /* Partition the filestruct so that it contains only the marked
     * text. */
    mark_order((const filestruct **)&top, &top_x,
1765
	(const filestruct **)&bot, &bot_x, NULL);
1766
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1767
1768

    /* If the line at filebot is blank, treat it as the magicline and
1769
1770
1771
1772
1773
     * hence the end of the file.  Otherwise, add a magicline and treat
     * it as the end of the file. */
    added_magicline = (filebot->data[0] != '\0');
    if (added_magicline)
	new_magicline();
1774

1775
    retval = write_file(name, tmp, append, TRUE);
1776

1777
1778
1779
1780
1781
1782
1783
1784
    /* If we added a magicline, remove it now. */
    if (added_magicline)
	remove_magicline();

    /* Unpartition the filestruct so that it contains all the text
     * again. */
    unpartition_filestruct(filepart);

1785
    if (old_modified)
1786
1787
1788
1789
1790
1791
	set_modified();

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

1792
int do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1793
{
1794
    int i;
1795
1796
1797
    int retval = 0, append = 0;
    char *ans;
	/* The last answer the user typed on the statusbar. */
1798
#ifdef NANO_EXTRA
1799
    static bool did_cred = FALSE;
1800
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1801

1802
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1803
    currshortcut = writefile_list;
1804
1805
#endif

1806
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1807
1808
1809
1810
1811
	retval = write_file(filename, FALSE, 0, FALSE);

	/* Write succeeded. */
	if (retval == 1)
	    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1812
1813
    }

1814
#ifndef NANO_SMALL
1815
    if (ISSET(MARK_ISSET) && !exiting)
1816
	ans = mallocstrcpy(NULL, "");
1817
1818
    else
#endif
1819
	ans = mallocstrcpy(NULL, filename);
1820
1821
1822
1823

    while (TRUE) {
	const char *msg;
#ifndef NANO_SMALL
1824
1825
	const char *formatstr, *backupstr;

1826
	if (fmt == DOS_FILE)
1827
	   formatstr = N_(" [DOS Format]");
1828
	else if (fmt == MAC_FILE)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1829
	   formatstr = N_(" [Mac Format]");
1830
1831
1832
	else
	   formatstr = "";

1833
	if (ISSET(BACKUP_FILE))
1834
	   backupstr = N_(" [Backup]");
1835
1836
1837
	else
	   backupstr = "";

1838
	/* Be nice to the translation folks. */
1839
	if (ISSET(MARK_ISSET) && !exiting) {
1840
	    if (append == 2)
1841
		msg = N_("Prepend Selection to File");
Chris Allegretta's avatar
Chris Allegretta committed
1842
	    else if (append == 1)
1843
		msg = N_("Append Selection to File");
1844
	    else
1845
		msg = N_("Write Selection to File");
1846
1847
	} else
#endif /* !NANO_SMALL */
1848
	if (append == 2)
1849
	    msg = N_("File Name to Prepend to");
Chris Allegretta's avatar
Chris Allegretta committed
1850
	else if (append == 1)
1851
	    msg = N_("File Name to Append to");
1852
	else
1853
	    msg = N_("File Name to Write");
1854

1855
1856
1857
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
1858
	i = statusq(!ISSET(RESTRICTED) || filename[0] == '\0',
1859
		writefile_list, ans,
1860
#ifndef NANO_SMALL
1861
		NULL, "%s%s%s", _(msg), formatstr, backupstr
1862
#else
1863
		"%s", _(msg)
1864
1865
1866
#endif
		);

1867
	if (i < 0) {
1868
	    statusbar(_("Cancelled"));
1869
1870
1871
1872
	    retval = -1;
	    break;
	} else {
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
1873

1874
#ifndef DISABLE_BROWSER
1875
1876
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
1877

1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
		currshortcut = writefile_list;

		if (tmp == NULL)
		    continue;
		free(answer);
		answer = tmp;

		/* We have a file now.  Get out of the statusbar prompt
		 * cleanly. */
		statusq_abort();
	    } else
1889
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1890
#ifndef NANO_SMALL
1891
	    if (i == TOGGLE_DOS_KEY) {
1892
		fmt = (fmt == DOS_FILE) ? NIX_FILE : DOS_FILE;
1893
1894
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1895
		fmt = (fmt == MAC_FILE) ? NIX_FILE : MAC_FILE;
1896
1897
1898
1899
1900
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1901
#endif /* !NANO_SMALL */
1902
1903
1904
1905
1906
1907
1908
	    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
1909

Chris Allegretta's avatar
Chris Allegretta committed
1910
#ifdef DEBUG
1911
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1912
#endif
1913
1914

#ifdef NANO_EXTRA
1915
1916
1917
1918
1919
1920
1921
	    if (exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0 && !did_cred) {
		do_credits();
		did_cred = TRUE;
		retval = -1;
		break;
	    }
1922
#endif
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
	    if (append == 0 && strcmp(answer, filename) != 0) {
		struct stat st;

		if (!stat(answer, &st)) {
		    i = do_yesno(FALSE, _("File exists, OVERWRITE ? "));
		    if (i == 0 || i == -1)
			continue;
		/* 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. */
		} else if (!ISSET(RESTRICTED) && filename[0] != '\0'
1936
#ifndef NANO_SMALL
1937
			&& (exiting || !ISSET(MARK_ISSET))
1938
#endif
1939
1940
1941
1942
1943
			) {
		    i = do_yesno(FALSE, _("Save file under DIFFERENT NAME ? "));
		    if (i == 0 || i == -1)
			continue;
		}
1944
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1945

1946
#ifndef NANO_SMALL
1947
1948
1949
1950
1951
1952
1953
	    /* 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. */
	    if (!ISSET(RESTRICTED) && !exiting && ISSET(MARK_ISSET))
		retval = write_marked(answer, FALSE, append);
	    else
1954
#endif /* !NANO_SMALL */
1955
		retval = write_file(answer, FALSE, append, FALSE);
1956

1957
#ifdef ENABLE_MULTIBUFFER
1958
1959
1960
1961
	    /* If we're not about to exit, update the current entry in
	     * the open_files structure. */
	    if (!exiting)
		add_open_file(TRUE);
1962
#endif
1963
1964
1965

	    break;
	}
1966
    } /* while (TRUE) */
1967
1968
1969

    free(ans);
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1970
1971
}

1972
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1973
{
1974
    do_writeout(FALSE);
1975
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1976
}
Chris Allegretta's avatar
Chris Allegretta committed
1977

1978
/* Return a malloc()ed string containing the actual directory, used
1979
1980
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1981
{
Chris Allegretta's avatar
Chris Allegretta committed
1982
    char *dirtmp = NULL;
1983

1984
    if (buf[0] == '~') {
1985
1986
1987
1988
1989
1990
1991
	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++)
	    ;

1992
1993
	/* Determine home directory using getpwuid() or getpwent(),
	   don't rely on $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
1994
1995
1996
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
1997
1998
1999
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
2000
			strncmp(userdata->pw_name, buf + 1, i - 1) != 0);
Chris Allegretta's avatar
Chris Allegretta committed
2001
2002
	}
	endpwent();
2003

2004
	if (userdata != NULL) {	/* User found */
2005
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2006
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
2007
	}
2008
    }
2009

2010
2011
2012
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

2013
    return dirtmp;
2014
2015
}

Chris Allegretta's avatar
Chris Allegretta committed
2016
#ifndef DISABLE_TABCOMP
2017
2018
2019
/* 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. */
2020
int append_slash_if_dir(char *buf, bool *lastwastab, int *place)
2021
{
2022
    char *dirptr = real_dir_from_tilde(buf);
2023
    struct stat fileinfo;
2024
    int ret = 0;
2025

2026
    assert(dirptr != buf);
2027

2028
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
2029
	strncat(buf, "/", 1);
2030
	(*place)++;
2031
	/* now we start over again with # of tabs so far */
2032
	*lastwastab = FALSE;
2033
	ret = 1;
2034
2035
    }

2036
    free(dirptr);
2037
    return ret;
2038
}
Chris Allegretta's avatar
Chris Allegretta committed
2039
2040

/*
Chris Allegretta's avatar
Chris Allegretta committed
2041
2042
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
2043
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
 *
 * 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)
{
2061
    char **matches = (char **)NULL;
2062
    char *matchline = NULL;
2063
    struct passwd *userdata;
2064

2065
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2066
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2067

2068
    strcat(buf, "*");
2069

2070
    while ((userdata = getpwent()) != NULL) {
2071

2072
	if (check_wildcard_match(userdata->pw_name, &buf[1])) {
2073
2074
2075
2076

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

2078
2079
2080
2081
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2082
	    if (operating_dir != NULL) {
2083
		if (check_operating_dir(userdata->pw_dir, TRUE) != 0)
2084
2085
2086
2087
		    continue;
	    }
#endif

2088
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2089
2090
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
2091
	    ++(*num_matches);
2092

2093
2094
2095
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2096
	}
2097
2098
    }
    endpwent();
2099

2100
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2101
2102
2103
2104
2105
2106
2107
}

/* 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
2108
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2109
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2110
2111
2112
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2113
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2114
2115
2116
2117

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

2118
    /* Okie, if there's a / in the buffer, strip out the directory part */
2119
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2120
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2121
2122
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2123
	    tmp--;
2124

Chris Allegretta's avatar
Chris Allegretta committed
2125
2126
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2127
2128
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2129

Chris Allegretta's avatar
Chris Allegretta committed
2130
    } else {
2131

Chris Allegretta's avatar
Chris Allegretta committed
2132
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2133
2134
2135
2136
2137
2138
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2139
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2140
2141
2142
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2143

Chris Allegretta's avatar
Chris Allegretta committed
2144
2145
2146
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2147
2148

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2149
    fprintf(stderr, "\nDir = %s\n", dirname);
2150
2151
2152
2153
2154
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2155
    dir = opendir(dirname);
2156
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2157
2158
2159
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2160
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2161
2162
2163
2164
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2165
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2166
2167
#endif
	/* See if this matches */
2168
	if (check_wildcard_match(next->d_name, tmp)) {
2169
2170
2171
2172

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2173
2174
2175
2176
2177
2178
2179
2180

#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 */

2181
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2182
2183
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2184
2185
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2186
		if (check_operating_dir(tmp2, TRUE) != 0) {
2187
2188
2189
2190
2191
2192
2193
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2194
	    tmp2 = NULL;
2195
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2196
2197
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2198
	    ++*num_matches;
2199
2200
2201
2202

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2203
2204
	}
    }
2205
2206
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2207

2208
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2209
2210
}

Chris Allegretta's avatar
Chris Allegretta committed
2211
2212
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
2213
2214
char *input_tab(char *buf, int place, bool *lastwastab, int *newplace,
	bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2215
2216
{
    /* Do TAB completion */
2217
    static int num_matches = 0, match_matches = 0;
2218
    static char **matches = (char **)NULL;
2219
    int pos = place, i = 0, col = 0, editline = 0;
2220
    int longestname = 0, is_dir = 0;
2221
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2222

2223
    *list = FALSE;
2224

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2225
    if (*lastwastab == FALSE) {
2226
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2227

2228
	*lastwastab = TRUE;
2229

Chris Allegretta's avatar
Chris Allegretta committed
2230
2231
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2232
2233
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2234

2235
2236
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2237
2238

	/* skip any leading white space */
2239
	while (*tmp && isblank(*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2240
2241
2242
2243
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2244
2245
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2246
	    free(matches);
2247
	    matches = (char **)NULL;
2248
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2249
2250
2251
2252
2253
	}

	/* 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
2254
2255
2256
2257
2258
	/* 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. */
2259
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2260
	    buf = mallocstrcpy(buf, tmp);
2261
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2262
	}
2263
2264
2265
2266
2267
2268
	/* 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
2269
2270
2271

	/* Try to match everything in the current working directory that
	 * matches.  */
2272
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2273
2274
2275
	    matches = cwd_tab_completion(tmp, &num_matches);

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

2278
2279
2280
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2281
	/* Did we find exactly one match? */
2282
	switch (num_matches) {
2283
	case 0:
2284
	    blank_edit();
2285
	    wrefresh(edit);
2286
2287
	    break;
	case 1:
2288

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

2291
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2292
2293
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2294
		tmp++;
2295
	    } else
2296
2297
		tmp = buf;

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

2301
	    if (is_dir != 0)
2302
		break;
2303
2304

	    copyto = tmp;
2305
2306
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2307
2308
		tmp++;

2309
	    /* write out the matched name */
2310
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2311
2312
	    *newplace += strlen(matches[0]) - pos;

2313
2314
2315
2316
2317
2318
2319
	    /* 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;

2320
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2321
	    append_slash_if_dir(buf, lastwastab, newplace);
2322

2323
2324
	    break;
	default:
2325
	    /* Check to see if all matches share a beginning, and, if so,
2326
	       tack it onto buf and then beep */
2327

2328
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2329
2330
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2331
		tmp++;
2332
	    } else
2333
2334
		tmp = buf;

2335
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2336
		 pos <= strlen(matches[0]); pos++)
2337
2338
		tmp++;

2339
	    while (TRUE) {
2340
2341
2342
2343
2344
2345
2346
2347
		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++;
		}
2348
2349
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2350
		    /* All the matches have the same character at pos+1,
2351
		       so paste it into buf... */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2352
		    buf = charealloc(buf, strlen(buf) + 2);
2353
		    strncat(buf, matches[0] + pos, 1);
2354
		    *newplace += 1;
2355
		    pos++;
2356
		} else {
2357
2358
2359
2360
2361
		    beep();
		    break;
		}
	    }
	}
Chris Allegretta's avatar
Chris Allegretta committed
2362
2363
2364
2365
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2366
	if (matches != NULL && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2367
2368
2369
2370
2371

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

2372
	    editline = 0;
2373

2374
2375
2376
2377
2378
2379
2380
2381
	    /* 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;

2382
	    foo = charalloc(longestname + 5);
2383

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

2387
		/* make each filename shown be the same length as the longest
2388
		   filename, with two spaces at the end */
2389
2390
2391
2392
2393
2394
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2395
2396
		/* Disable el cursor */
		curs_set(0);
2397
2398
2399
2400
2401
2402
		/* 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 */
2403
		if (col > COLS - longestname && i + 1 < num_matches) {
2404
2405
		    editline++;
		    wmove(edit, editline, 0);
2406
2407
2408
2409
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2410
2411
2412
		    col = 0;
		}
	    }
2413
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2414
	    wrefresh(edit);
2415
	    *list = TRUE;
2416
2417
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2418
2419
    }

2420
2421
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
2422
    if (*list == FALSE)
2423
	edit_refresh();
2424
2425
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2426
}
2427
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2428

2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
/* 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;
}

2444
#ifndef DISABLE_BROWSER
2445
/* Our sort routine for file listings -- sort directories before
2446
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2447
2448
int diralphasort(const void *va, const void *vb)
{
2449
2450
2451
2452
    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
2453

2454
    if (aisdir != 0 && bisdir == 0)
2455
	return -1;
2456
    if (aisdir == 0 && bisdir != 0)
2457
	return 1;
2458

2459
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2460
2461
}

2462
/* Free our malloc()ed memory */
2463
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2464
{
2465
2466
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2467
2468
2469
    free(array);
}

2470
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2471
2472
void striponedir(char *foo)
{
2473
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2474

2475
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2476
    /* Don't strip the root dir */
2477
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2478
2479
	return;

2480
2481
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2482
    if (*tmp == '/')
2483
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2484
2485
2486
2487
2488

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

    if (tmp != foo)
2489
2490
2491
2492
2493
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2494
    }
Chris Allegretta's avatar
Chris Allegretta committed
2495
2496
}

2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
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;
}

2507
/* Initialize the browser code, including the list of files in *path */
2508
char **browser_init(const char *path, int *longest, int *numents)
2509
2510
2511
{
    DIR *dir;
    struct dirent *next;
2512
    char **filelist;
2513
    int i = 0;
2514
    size_t path_len;
2515
2516

    dir = opendir(path);
2517
    if (dir == NULL)
2518
2519
2520
2521
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
2522
	if (strcmp(next->d_name, ".") == 0)
2523
2524
2525
2526
2527
2528
2529
2530
	   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
2531
    filelist = (char **)nmalloc(*numents * sizeof (char *));
2532

2533
    if (strcmp(path, "/") == 0)
2534
2535
2536
	path = "";
    path_len = strlen(path);

2537
    while ((next = readdir(dir)) != NULL) {
2538
	if (strcmp(next->d_name, ".") == 0)
2539
2540
	   continue;

2541
2542
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2543
2544
	i++;
    }
2545
    closedir(dir);
2546
2547
2548
2549
2550
2551
2552

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2553
/* Our browser function.  inpath is the path to start browsing from */
2554
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2555
2556
2557
2558
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2559
2560
2561
    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;
2562
    bool meta_key, func_key;
2563
    char **filelist = (char **)NULL;
2564
#ifndef DISABLE_MOUSE
2565
2566
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2567

2568
2569
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2570
2571
2572
    /* 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 */
2573
    if (path != NULL && strcmp(path, inpath) != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2574
2575
2576
2577
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2578
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2579
    if (path == NULL)
2580
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2581
2582

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

2585
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2586
2587
2588
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2589
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2590
2591
2592
2593
2594
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2595
2596

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2597
    do {
2598
2599
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2600

2601
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2602

2603
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2604
	currshortcut = browser_list;
2605
2606
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2607
2608
 	editline = 0;
	col = 0;
2609
	    
2610
	/* Compute line number we're on now, so we don't divide by zero later */
2611
2612
2613
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2614
2615

	switch (kbinput) {
2616

2617
#ifndef DISABLE_MOUSE
2618
	case KEY_MOUSE:
2619
	    if (getmouse(&mevent) == ERR)
2620
		return retval;
2621
2622
2623
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2624
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2625
2626
2627
2628
		int selectedbackup = selected;

		mevent.y -= 2;

2629
2630
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2631
		selected = (lineno / editwinrows) * editwinrows * width
2632
2633
2634
2635
2636
2637
			+ 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--;
2638
2639
2640

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2641
		if (selected > numents - 1)
2642
		    selected = numents - 1;
2643
		else if (selectedbackup == selected)
2644
2645
2646
2647
		    /* Unget the 'select' key */
		    unget_kbinput('s', FALSE, FALSE);
	    } else {
		/* Must be clicking a shortcut */
2648
2649
2650
		int mouse_x, mouse_y;
		get_mouseinput(&mouse_x, &mouse_y, TRUE);
	    }
2651

2652
2653
            break;
#endif
2654
	case NANO_PREVLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2655
2656
2657
	    if (selected - width >= 0)
		selected -= width;
	    break;
2658
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2659
2660
2661
	    if (selected > 0)
		selected--;
	    break;
2662
	case NANO_NEXTLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2663
2664
2665
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
2666
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2667
2668
2669
2670
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2671
	case NANO_PREVPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2672
	case '-': /* Pico compatibility */
2673
	    if (selected >= (editwinrows + lineno % editwinrows) * width)
2674
		selected -= (editwinrows + lineno % editwinrows) * width;
Chris Allegretta's avatar
Chris Allegretta committed
2675
2676
2677
2678
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2679
	case NANO_NEXTPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2680
	case ' ': /* Pico compatibility */
2681
2682
	    selected += (editwinrows - lineno % editwinrows) * width;
	    if (selected >= numents)
Chris Allegretta's avatar
Chris Allegretta committed
2683
2684
		selected = numents - 1;
	    break;
2685
2686
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2687
	case '?': /* Pico compatibility */
2688
#ifndef DISABLE_HELP
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2689
	    do_help();
2690
	    curs_set(0);
2691
2692
2693
#else
	    nano_disabled_msg();
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2694
	    break;
2695
	case NANO_ENTER_KEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2696
2697
	case 'S': /* Pico compatibility */
	case 's':
Chris Allegretta's avatar
Chris Allegretta committed
2698
	    /* You can't cd up from / */
2699
2700
	    if (strcmp(filelist[selected], "/..") == 0 &&
		strcmp(path, "/") == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2701
		statusbar(_("Can't move up a directory"));
2702
		beep();
Rocco Corsi's avatar
   
Rocco Corsi committed
2703
2704
2705
		break;
	    }

2706
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2707
2708
2709
	    /* 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. */
2710
	    if (check_operating_dir(filelist[selected], FALSE) != 0) {
2711
2712
		statusbar(_("Can't go outside of %s in restricted mode"),
			operating_dir);
2713
2714
		beep();
		break;
2715
2716
2717
	    }
#endif

2718
	    if (stat(filelist[selected], &st) == -1) {
2719
2720
		statusbar(_("Can't open \"%s\": %s"), filelist[selected],
			strerror(errno));
2721
2722
2723
		beep();
		break;
	    }
2724

2725
2726
2727
2728
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2729
2730
	    }

2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
	    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
2741
		}
2742
2743
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2744

2745
2746
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
2747
2748
		statusbar(_("Can't open \"%s\": %s"), new_path,
			strerror(errno));
2749
2750
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2751
	    }
2752
2753
2754
2755
2756
2757
2758

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

2759
	/* Go to a specific directory */
2760
2761
	case NANO_GOTOLINE_KEY:
	case NANO_GOTOLINE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2762
2763
	case 'G': /* Pico compatibility */
	case 'g':
Rocco Corsi's avatar
   
Rocco Corsi committed
2764
	    curs_set(1);
2765
	    j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2766
#ifndef NANO_SMALL
2767
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2768
#endif
2769
		_("Go To Directory"));
2770
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2771
2772
2773
	    curs_set(0);

	    if (j < 0) {
2774
		statusbar(_("Cancelled"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2775
2776
2777
		break;
	    }

2778
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2779

2780
2781
2782
	    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
2783
2784
	    }

2785
#ifndef DISABLE_OPERATINGDIR
2786
	    if (check_operating_dir(new_path, FALSE) != 0) {
2787
2788
2789
2790
2791
2792
2793
		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
2794
2795
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2796
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2797
		break;
2798
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2799
2800

	    /* Start over again with the new path value */
2801
2802
	    free_charptrarray(filelist, numents);
	    free(foo);
2803
2804
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2805
2806
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2807
	/* Stuff we want to abort the browser */
2808
	case NANO_CANCEL_KEY:
2809
	case NANO_EXIT_KEY:
2810
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2811
2812
	case 'E': /* Pico compatibility */
	case 'e':
2813
2814
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2815
2816
2817
2818
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2819
2820
	blank_edit();

2821
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
	    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 */
2836
2837
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2838
2839
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2840
2841
2842
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2843
2844
2845
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
2846
		    stat(filelist[j], &st);
2847
2848
2849
2850
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2851
2852
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2853
			(int) st.st_size);
2854
2855
2856
2857
2858
2859
		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);
2860
		else /* It's more than 1 k and less than a meg */
2861
2862
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2863
2864
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2865
	    /* Highlight the currently selected file/dir */
2866
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2867
		wattron(edit, A_REVERSE);
2868
2869
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2870
2871
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2872
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2873
2874
2875
2876
2877
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2878
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2879
2880
2881
2882
2883
2884
2885
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
2886
	wrefresh(edit);
2887
2888
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
Chris Allegretta's avatar
Chris Allegretta committed
2889
2890
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2891
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2892
2893
    edit_refresh();

Chris Allegretta's avatar
Chris Allegretta committed
2894
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2895
2896
2897
2898
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2899

2900
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2901
 starts do_browser from there, else from the current dir */
2902
char *do_browse_from(const char *inpath)
2903
2904
{
    struct stat st;
2905
    char *bob;
2906
2907
2908
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2909

2910
2911
2912
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2913

2914
2915
2916
2917
2918
2919
2920
2921
    /*
     * 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);
2922
2923
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2924
	    path = getcwd(NULL, PATH_MAX + 1);
2925
	}
2926
    }
2927

2928
2929
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2930
    if (check_operating_dir(path, FALSE) != 0)
2931
2932
2933
2934
2935
2936
2937
2938
2939
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2940
    return bob;
2941
}
Chris Allegretta's avatar
Chris Allegretta committed
2942
#endif /* !DISABLE_BROWSER */
2943

2944
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2945
2946
2947
/* 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)
2948
{
2949
    char *nanohist = NULL;
2950

2951
2952
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2953

2954
2955
2956
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2957
    }
2958
2959
2960
2961
2962
2963
    return nanohist;
}

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

2965
    /* assume do_rcfile() has reported missing home dir */
2966
2967
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2968

2969
	if (hist == NULL) {
2970
	    if (errno != ENOENT) {
2971
2972
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2973
		rcfile_error(N_("Error reading %s: %s"), nanohist, strerror(errno));
2974
2975
2976
		fprintf(stderr, _("\nPress Return to continue starting nano\n"));
		while (getchar() != '\n')
		    ;
2977
	    }
2978
	} else {
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
	    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
2993
2994
2995
		    history = &replace_history;
	    }
	    fclose(hist);
2996
	    free(line);
2997
2998
	    UNSET(HISTORY_CHANGED);
	}
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
	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;
3015
    }
3016
    return TRUE;
3017
3018
3019
3020
3021
}

/* save histories to ~/.nano_history */
void save_history(void)
{
3022
    char *nanohist;
3023
3024

    /* don't save unchanged or empty histories */
3025
    if ((search_history.count == 0 && replace_history.count == 0) ||
3026
	!ISSET(HISTORY_CHANGED) || ISSET(VIEW_MODE))
3027
3028
	return;

3029
3030
3031
3032
    nanohist = histfilename();

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

3034
	if (hist == NULL)
3035
	    rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
3036
	else {
3037
3038
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
3039
3040
3041
3042

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
3043
		rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
3044
3045
3046
3047
3048
	    fclose(hist);
	}
	free(nanohist);
    }
}
3049
#endif /* !NANO_SMALL && ENABLE_NANORC */