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

581
582
583
	    if (execute)
		execute_command(answer);
	    else {
584
#endif
585
		answer = mallocstrassn(answer, real_dir_from_tilde(answer));
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600

#ifdef ENABLE_MULTIBUFFER
		if (!ISSET(MULTIBUFFER)) {
#endif
		    /* 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,
			current, current_x);
		    edittop = fileage;
#ifdef ENABLE_MULTIBUFFER
		}
#endif

601
		load_buffer(answer);
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622

#ifdef ENABLE_MULTIBUFFER
		if (!ISSET(MULTIBUFFER))
#endif
		{
		    filestruct *top_save = fileage;

		    /* 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. */
		    unpartition_filestruct(filepart);

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

		    /* Set edittop back to what it was before. */
		    edittop = edittop_save;
		}
623
#ifndef NANO_SMALL
624
	    }
625
#endif
626

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

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

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

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

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

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

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

    display_main_list();
}

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

    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,
689
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
690
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
{
    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
731
	    fprintf(stderr, "%s: free'd a node, YAY!\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
732
733
734
735
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
736
	fprintf(stderr, "%s: free'd last node.\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
737
738
739
740
#endif
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    }

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

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

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

    }

    load_open_file();

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

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

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

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

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

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

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

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

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

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

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

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

	}
    }

    load_open_file();

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

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

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

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

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

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

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

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

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

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

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

    d_here = getcwd(NULL, PATH_MAX + 1);

1062
    if (d_here != NULL) {
1063
1064

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

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

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

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

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

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

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

		free(d_there);

		d_there = getcwd(NULL, PATH_MAX + 1);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1308
    assert(full_operating_dir != NULL);
1309
1310

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1489
	/* Open the original file to copy to the backup. */
1490
	f = fopen(realname, "rb");
1491
	if (f == NULL) {
1492
	    statusbar(_("Error reading %s: %s"), realname,
1493
		strerror(errno));
1494
	    goto cleanup_and_exit;
1495
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
	/* 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);
	}
1529

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    retval = 1;

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

1744
1745
1746
#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
1747
1748
1749
1750
 * 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. */
1751
int write_marked(const char *name, bool tmp, int append)
1752
1753
{
    int retval = -1;
1754
    bool old_modified = ISSET(MODIFIED);
1755
	/* write_file() unsets the MODIFIED flag. */
1756
1757
1758
1759
1760
1761
1762
1763
    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,
1764
	(const filestruct **)&bot, &bot_x, NULL);
1765
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1766
1767

    /* If the line at filebot is blank, treat it as the magicline and
1768
1769
1770
1771
1772
     * 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();
1773

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

1776
1777
1778
1779
1780
1781
1782
1783
    /* 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);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
		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
1888
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1889
#ifndef NANO_SMALL
1890
	    if (i == TOGGLE_DOS_KEY) {
1891
		fmt = (fmt == DOS_FILE) ? NIX_FILE : DOS_FILE;
1892
1893
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1894
		fmt = (fmt == MAC_FILE) ? NIX_FILE : MAC_FILE;
1895
1896
1897
1898
1899
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1900
#endif /* !NANO_SMALL */
1901
1902
1903
1904
1905
1906
1907
	    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
1908

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

#ifdef NANO_EXTRA
1914
1915
1916
1917
1918
1919
1920
	    if (exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0 && !did_cred) {
		do_credits();
		did_cred = TRUE;
		retval = -1;
		break;
	    }
1921
#endif
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
	    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'
1935
#ifndef NANO_SMALL
1936
			&& (exiting || !ISSET(MARK_ISSET))
1937
#endif
1938
1939
1940
1941
1942
			) {
		    i = do_yesno(FALSE, _("Save file under DIFFERENT NAME ? "));
		    if (i == 0 || i == -1)
			continue;
		}
1943
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1944

1945
#ifndef NANO_SMALL
1946
1947
1948
1949
1950
1951
1952
	    /* 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
1953
#endif /* !NANO_SMALL */
1954
		retval = write_file(answer, FALSE, append, FALSE);
1955

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

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

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

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

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

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

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

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

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

2012
    return dirtmp;
2013
2014
}

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

2025
    assert(dirptr != buf);
2026

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

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

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

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

2067
    strcat(buf, "*");
2068

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

2222
    *list = FALSE;
2223

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

2227
	*lastwastab = TRUE;
2228

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2371
	    editline = 0;
2372

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

2381
	    foo = charalloc(longestname + 5);
2382

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

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

		strcat(foo, "  ");

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return filelist;
}

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

2567
2568
    assert(inpath != NULL);

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

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

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

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

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

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

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

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

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

	switch (kbinput) {
2615

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

		mevent.y -= 2;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2909
2910
2911
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2912

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

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

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

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

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

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

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

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

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

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

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

3028
3029
3030
3031
    nanohist = histfilename();

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

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

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