files.c 79.1 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-2005 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
24
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
25

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

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

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

184
#ifndef NANO_SMALL
185
186
	    /* 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
187
188
	     * currently think it's a Mac file. */
	    if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r' &&
189
190
		(format == 0 || format == 2))
		format++;
191
192
193
194
#endif

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

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

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

208
209
210
211
212
	    /* 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;
213
214
215

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

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

Chris Allegretta's avatar
Chris Allegretta committed
222
	    num_lines++;
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

241
	    buf[i] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
242
	    buf[i + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
243
244
	    i++;
	}
245
246
    }

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
	len = 1;

259
260
	buf[0] = input;
	buf[1] = '\0';
261
262
    }
#endif
263

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

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

281
282
283
284
    free(buf);

    /* If we didn't get a file and we don't already have one, make a new
     * file. */
285
    if (fileptr == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
286
	new_file();
Robert Siemborski's avatar
Robert Siemborski committed
287

288
    /* Did we try to insert a file of 0 bytes? */
289
290
291
292
293
294
295
296
297
298
    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();
299
	    totsize--;
300
	}
Chris Allegretta's avatar
Chris Allegretta committed
301
    }
302

303
304
305
    get_totals(fileage, filebot, NULL, &num_chars);
    totsize += num_chars;

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

327
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
328
329
}

330
331
332
333
334
335
336
337
/* 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
338
339
340
341
{
    int fd;
    struct stat fileinfo;

342
    assert(f != NULL);
343

344
345
346
    if (filename == NULL || filename[0] == '\0' ||
	    stat(filename, &fileinfo) == -1) {
	if (newfie) {
Chris Allegretta's avatar
Chris Allegretta committed
347
	    statusbar(_("New File"));
348
	    return -2;
Chris Allegretta's avatar
Chris Allegretta committed
349
	}
350
351
	statusbar(_("\"%s\" not found"), filename);
	return -1;
352
353
354
    } 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! */
355
356
	statusbar(S_ISDIR(fileinfo.st_mode) ? _("\"%s\" is a directory")
		: _("File \"%s\" is a device file"), filename);
357
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
358
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
359
360
361
	statusbar(_("Error reading %s: %s"), filename, strerror(errno));
 	return -1;
     } else {
362
363
364
	/* File is A-OK.  Open it in binary mode for our own end-of-line
	 * character munging. */
	*f = fdopen(fd, "rb");
365
366

	if (*f == NULL) {
367
368
	    statusbar(_("Error reading %s: %s"), filename,
		strerror(errno));
369
	    close(fd);
370
371
	} else
	    statusbar(_("Reading File"));
Chris Allegretta's avatar
Chris Allegretta committed
372
    }
373
    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
374
375
}

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

386
    buf = charalloc(namelen + num_of_digits(INT_MAX) + 7);
387
    strcpy(buf, name);
388
389
    strcpy(buf + namelen, ".save");
    namelen += 5;
390

391
    while (TRUE) {
392
	struct stat fs;
393
394

	if (stat(buf, &fs) == -1)
Chris Allegretta's avatar
Chris Allegretta committed
395
	    return buf;
396
397
398
399
	if (i == INT_MAX)
	    break;

	i++;
400
	sprintf(buf + namelen, ".%d", i);
401
402
    }

Chris Allegretta's avatar
Chris Allegretta committed
403
    /* We get here only if there is no possible save file. */
404
    null_at(&buf, 0);
405
406
407
    return buf;
}

408
409
#ifndef NANO_SMALL
void execute_command(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
410
{
411
412
413
414
415
416
417
#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);
418
    }
419
420
421
422
423
424
425
426
427
#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
428

429
430
431
432
/* 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
433
    bool new_buffer = (fileage == NULL
434
435
#ifdef ENABLE_MULTIBUFFER
	 || ISSET(MULTIBUFFER)
436
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
437
	);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
438
439
440
441
	/* 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. */
442
443
444
445
    FILE *f;
    int rc;
	/* rc == -2 means that the statusbar displayed "New File".  -1
	 * means that the open failed.  0 means success. */
446

447
448
#ifndef DISABLE_OPERATINGDIR
    if (check_operating_dir(name, FALSE)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
449
450
	statusbar(_("Can't insert file from outside of %s"),
		operating_dir);
451
452
	return;
    }
453
454
#endif

455
456
457
#ifdef ENABLE_MULTIBUFFER
    /* Update the current entry in the open_files structure. */
    add_open_file(TRUE);
458
459
#endif

460
461
462
463
464
    rc = open_file(name, new_buffer, &f);

#ifdef ENABLE_MULTIBUFFER
    if (rc != -1 && ISSET(MULTIBUFFER)) {
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
465
#ifndef NANO_SMALL
466
	UNSET(MARK_ISSET);
Chris Allegretta's avatar
Chris Allegretta committed
467
#endif
468
    }
469
#endif
470
471
472
473
474
475
476

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

    if (rc == 0) {
477
478
	file_format fmt_save = fmt;

479
	read_file(f, filename);
480
481
482
483
484
485

	/* 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
486
#ifndef NANO_SMALL
487
	stat(filename, &originalfilestat);
Chris Allegretta's avatar
Chris Allegretta committed
488
#endif
489
490
491
492
493
494
495
496
    }

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

497
498
499
500
501
502
503
void do_insertfile(
#ifndef NANO_SMALL
	bool execute
#else
	void
#endif
	)
504
505
506
{
    int i;
    const char *msg;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
507
    char *ans = mallocstrcpy(NULL, "");
508
	/* The last answer the user typed on the statusbar. */
509
    filestruct *edittop_save = edittop;
510
    int current_y_save = current_y;
511
512
    bool at_edittop = FALSE;
	/* Whether we're at the top of the edit window. */
513

514
#ifndef DISABLE_WRAPPING
515
    wrap_reset();
516
#endif
517

518
    while (TRUE) {
519
#ifndef NANO_SMALL
520
	if (execute) {
521
#ifdef ENABLE_MULTIBUFFER
522
523
524
	    if (ISSET(MULTIBUFFER))
		msg = N_("Command to execute in new buffer [from %s] ");
	    else
525
#endif
526
527
		msg = N_("Command to execute [from %s] ");
	} else {
528
529
#endif
#ifdef ENABLE_MULTIBUFFER
530
531
532
	    if (ISSET(MULTIBUFFER)) {
		msg = N_("File to insert into new buffer [from %s] ");
	    } else
533
#endif
534
		msg = N_("File to insert [from %s] ");
535
536
#ifndef NANO_SMALL
	}
537
#endif
538

539
	i = statusq(TRUE,
540
#ifndef NANO_SMALL
541
		execute ? extcmd_list :
542
543
#endif
		insertfile_list, ans,
Chris Allegretta's avatar
Chris Allegretta committed
544
#ifndef NANO_SMALL
545
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
546
#endif
547
548
549
550
		_(msg),
#ifndef DISABLE_OPERATINGDIR
		operating_dir != NULL && strcmp(operating_dir, ".") != 0 ?
		operating_dir :
551
#endif
552
		"./");
553

554
555
	/* If we're in multibuffer mode and the filename or command is
	 * blank, open a new buffer instead of canceling. */
556
	if (i == -1 || (i == -2
557
#ifdef ENABLE_MULTIBUFFER
558
		&& !ISSET(MULTIBUFFER)
559
#endif
560
		))
561
	{
562
563
564
	    statusbar(_("Cancelled"));
	    break;
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
565
	    size_t pww_save = placewewant;
566

567
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
568

569
#if !defined(NANO_SMALL) && defined(ENABLE_MULTIBUFFER)
570
571
572
573
574
575
	    if (i == TOGGLE_MULTIBUFFER_KEY) {
		/* Don't allow toggling if we're in view mode. */
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
		continue;
	    }
576
#endif
577

578
#ifndef DISABLE_BROWSER
579
580
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
581

582
583
		if (tmp == NULL)
		    continue;
584
585
		free(answer);
		answer = tmp;
586
587
588
589

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

593
#ifndef NANO_SMALL
594
595
596
597
	    if (i == NANO_TOOTHERINSERT_KEY) {
		execute = !execute;
		continue;
	    }
598
#endif
599
600

#ifdef ENABLE_MULTIBUFFER
601
	    if (!ISSET(MULTIBUFFER)) {
602
#endif
603
604
		/* If we're not inserting into a new buffer, partition
		 * the filestruct so that it contains no text and hence
605
606
607
		 * looks like a new buffer, and keep track of whether
		 * the top of the partition is the top of the edit
		 * window. */
608
		filepart = partition_filestruct(current, current_x,
609
			current, current_x);
610
		at_edittop = (fileage == edittop);
611
#ifdef ENABLE_MULTIBUFFER
612
	    }
613
614
#endif

615
616
617
618
619
620
#ifndef NANO_SMALL
	    if (execute)
		execute_command(answer);
	    else {
#endif
		answer = mallocstrassn(answer, real_dir_from_tilde(answer));
621
		load_buffer(answer);
622
623
624
#ifndef NANO_SMALL
	    }
#endif
625
626

#ifdef ENABLE_MULTIBUFFER
627
	    if (!ISSET(MULTIBUFFER))
628
#endif
629
630
	    {
		filestruct *top_save = fileage;
631

632
633
634
635
636
637
638
639
640
		/* If we didn't insert into a new buffer, and we were at
		 * the top of the edit window before, set the saved
		 * value of edittop to the new top of the edit window,
		 * and update the current y-coordinate to account for
		 * the number of lines inserted. */
		if (at_edittop)
		    edittop_save = fileage;
		current_y += current_y_save;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
641
		/* If we didn't insert into a new buffer, unpartition
642
643
644
645
		 * the filestruct so that it contains all the text
		 * again.  Note that we've replaced the non-text
		 * originally in the partition with the text in the
		 * inserted file/executed command output. */
646
		unpartition_filestruct(&filepart);
647

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

652
653
		/* Set edittop back to what it was before. */
		edittop = edittop_save;
654
	    }
655

656
#ifdef ENABLE_MULTIBUFFER
657
658
659
	    if (ISSET(MULTIBUFFER)) {
		/* Update the titlebar. */
		titlebar(NULL);
660

661
662
663
		/* Reinitialize the shortcut list. */
		shortcut_init(FALSE);
	    } else {
664
#endif
665
666
		/* Mark the file as modified. */
		set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
667

668
		/* Restore the old place we want. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
669
		placewewant = pww_save;
670
#ifdef ENABLE_MULTIBUFFER
671
	    }
Chris Allegretta's avatar
Chris Allegretta committed
672
673
#endif

674
675
676
677
678
679
	    /* Refresh the screen. */
	    edit_refresh();

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
681
    free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
682
683
}

684
void do_insertfile_void(void)
685
{
686
#ifdef ENABLE_MULTIBUFFER
687
688
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
	statusbar(_("Key illegal in non-multibuffer mode"));
689
    else
690
#endif
691
692
693
694
695
	do_insertfile(
#ifndef NANO_SMALL
		FALSE
#endif
		);
696
697
698
699

    display_main_list();
}

700
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
701
/* Create a new openfilestruct node. */
702
openfilestruct *make_new_opennode(void)
Chris Allegretta's avatar
Chris Allegretta committed
703
{
704
705
    openfilestruct *newnode =
	(openfilestruct *)nmalloc(sizeof(openfilestruct));
Chris Allegretta's avatar
Chris Allegretta committed
706
707
708
709
710
711
    newnode->filename = NULL;
    return newnode;
}

/* Splice a node into an existing openfilestruct. */
void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
712
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
713
{
714
    assert(newnode != NULL && begin != NULL);
715

Chris Allegretta's avatar
Chris Allegretta committed
716
717
718
719
720
721
722
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

723
724
/* Unlink a node from the rest of the openfilestruct, and delete it. */
void unlink_opennode(openfilestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
725
{
726
    assert(fileptr != NULL && fileptr->prev != NULL && fileptr->next != NULL && fileptr != fileptr->prev && fileptr != fileptr->next);
727

728
729
730
    fileptr->prev->next = fileptr->next;
    fileptr->next->prev = fileptr->prev;
    delete_opennode(fileptr);
Chris Allegretta's avatar
Chris Allegretta committed
731
732
733
734
735
}

/* Delete a node from the openfilestruct. */
void delete_opennode(openfilestruct *fileptr)
{
736
    assert(fileptr != NULL && fileptr->filename != NULL && fileptr->fileage != NULL);
737

738
739
740
    free(fileptr->filename);
    free_filestruct(fileptr->fileage);
    free(fileptr);
Chris Allegretta's avatar
Chris Allegretta committed
741
742
}

743
744
745
#ifdef DEBUG
/* Deallocate all memory associated with this and later files, including
 * the lines of text. */
Chris Allegretta's avatar
Chris Allegretta committed
746
747
void free_openfilestruct(openfilestruct *src)
{
748
    assert(src != NULL);
749

750
751
752
    while (src != src->next) {
	src = src->next;
	delete_opennode(src->prev);
Chris Allegretta's avatar
Chris Allegretta committed
753
    }
754
    delete_opennode(src);
Chris Allegretta's avatar
Chris Allegretta committed
755
}
756
#endif
Chris Allegretta's avatar
Chris Allegretta committed
757

758
/* Add/update an entry to the open_files openfilestruct.  If update is
759
 * FALSE, a new entry is created; otherwise, the current entry is
760
 * updated. */
761
void add_open_file(bool update)
762
{
763
    if (open_files == NULL && update)
764
	return;
765

766
767
768
769
770
771
772
773
774
    /* If there are no entries in open_files, make the first one. */
    if (open_files == NULL) {
	open_files = make_new_opennode();
	splice_opennode(open_files, open_files, open_files);
    /* Otherwise, if we're not updating, make a new entry for
     * open_files and splice it in after the current entry. */
    } else if (!update) {
	splice_opennode(open_files, make_new_opennode(),
		open_files->next);
775
776
777
	open_files = open_files->next;
    }

778
    /* Save the current filename. */
779
    open_files->filename = mallocstrcpy(open_files->filename, filename);
780

781
#ifndef NANO_SMALL
782
    /* Save the current file's stat. */
783
784
785
    open_files->originalfilestat = originalfilestat;
#endif

786
787
788
789
790
791
792
793
794
    /* Save the current file buffer. */
    open_files->fileage = fileage;
    open_files->filebot = filebot;

    /* Save the current top of the edit window. */
    open_files->edittop = edittop;

    /* Save the current line. */
    open_files->current = current;
795

796
797
    /* Save the current cursor position. */
    open_files->current_x = current_x;
798

799
800
    /* Save the current place we want. */
    open_files->placewewant = placewewant;
801

802
803
    /* Save the current total number of lines. */
    open_files->totlines = totlines;
804

805
806
    /* Save the current total size. */
    open_files->totsize = totsize;
807

808
809
    /* Start with no flags saved. */
    open_files->flags = 0;
810

811
812
813
    /* Save the current modification status. */
    if (ISSET(MODIFIED))
	open_files->flags |= MODIFIED;
814

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
815
#ifndef NANO_SMALL
816
817
818
819
820
    /* Save the current marking status and mark, if applicable. */
    if (ISSET(MARK_ISSET)) {
	open_files->flags |= MARK_ISSET;
	open_files->mark_beginbuf = mark_beginbuf;
	open_files->mark_beginx = mark_beginx;
Chris Allegretta's avatar
Chris Allegretta committed
821
822
    }

823
824
825
    /* Save the current file format. */
    open_files->fmt = fmt;
#endif
826
827

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

832
/* Read the current entry in the open_files structure and set up the
833
 * currently open file buffer using that entry's information. */
834
void load_open_file(void)
835
{
836
    assert(open_files != NULL);
837

838
    /* Restore the current filename. */
839
    filename = mallocstrcpy(filename, open_files->filename);
840

841
#ifndef NANO_SMALL
842
    /* Restore the current file's stat. */
843
844
    originalfilestat = open_files->originalfilestat;
#endif
845
846

    /* Restore the current file buffer. */
847
    fileage = open_files->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
848
    filebot = open_files->filebot;
849

850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
    /* Restore the current top of the edit window. */
    edittop = open_files->edittop;

    /* Restore the current line. */
    current = open_files->current;

    /* Restore the current cursor position. */
    current_x = open_files->current_x;

    /* Restore the current place we want. */
    placewewant = open_files->placewewant;

    /* Restore the current total number of lines. */
    totlines = open_files->totlines;

    /* Restore the current total size. */
    totsize = open_files->totsize;

    /* Restore the current modification status. */
    if (open_files->flags & MODIFIED)
Chris Allegretta's avatar
Chris Allegretta committed
870
871
872
873
874
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

#ifndef NANO_SMALL
875
876
877
878
    /* Restore the current marking status and mark, if applicable. */
    if (open_files->flags & MARK_ISSET) {
	mark_beginbuf = open_files->mark_beginbuf;
	mark_beginx = open_files->mark_beginx;
Chris Allegretta's avatar
Chris Allegretta committed
879
880
881
	SET(MARK_ISSET);
    } else
	UNSET(MARK_ISSET);
882

883
884
    /* Restore the current file format. */
    fmt = open_files->fmt;
Chris Allegretta's avatar
Chris Allegretta committed
885
#endif
886

Chris Allegretta's avatar
Chris Allegretta committed
887
888
889
#ifdef ENABLE_COLOR
    update_color();
#endif
890
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
891

892
    /* Update the titlebar. */
893
894
895
896
    clearok(topwin, FALSE);
    titlebar(NULL);
}

897
/* Open either the next or previous file buffer. */
898
void open_prevnext_file(bool next)
899
{
900
    add_open_file(TRUE);
901

902
    assert(open_files != NULL);
903

904
905
    /* If only one file buffer is open, indicate it on the statusbar and
     * get out. */
906
    if (open_files == open_files->next) {
907
	statusbar(_("No more open file buffers"));
908
	return;
909
910
    }

911
912
913
    /* Switch to the next or previous file, depending on the value of
     * next. */
    open_files = next ? open_files->next : open_files->prev;
914
915

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

919
    /* Load the file we switched to. */
920
921
    load_open_file();

922
    /* And indicate the switch on the statusbar. */
923
    statusbar(_("Switched to %s"),
924
      ((open_files->filename[0] == '\0') ? _("New Buffer") :
925
	open_files->filename));
926

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

932
933
/* Open the previous entry in the open_files structure.  This function
 * is used by the shortcut list. */
934
void open_prevfile_void(void)
935
{
936
    open_prevnext_file(FALSE);
937
938
}

939
940
/* Open the next entry in the open_files structure.  This function is
 * used by the shortcut list. */
941
void open_nextfile_void(void)
942
{
943
    open_prevnext_file(TRUE);
944
945
}

946
947
/* Delete an entry from the open_files filestruct.  After deletion of an
 * entry, the next entry is opened.  Return TRUE on success or FALSE if
948
 * there are no more open file buffers. */
949
bool close_open_file(void)
950
{
951
    assert(open_files != NULL);
952

953
954
    /* If only one file is open, get out. */
    if (open_files == open_files->next)
955
	return FALSE;
956

957
958
    /* Open the next file. */
    open_nextfile_void();
Chris Allegretta's avatar
Chris Allegretta committed
959

960
961
    /* Close the file we had open before. */
    unlink_opennode(open_files->prev);
962

963
    /* Reinitialize the shortcut list. */
964
    shortcut_init(FALSE);
965
    display_main_list();
966

967
    return TRUE;
968
}
969
#endif /* ENABLE_MULTIBUFFER */
970

971
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
972
/*
973
974
975
976
977
978
 * 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).
979
 */
980
char *get_full_path(const char *origpath)
981
{
982
983
984
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
985
    char *expanded_origpath;
986

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

    d_here = getcwd(NULL, PATH_MAX + 1);

992
    if (d_here != NULL) {
993
994

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

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

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

1012
1013
1014
1015
1016
1017
	/* 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
1018
		d_there = charealloc(d_there, strlen(d_there) + 2);
1019
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1020
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1021
1022
1023
1024
		strcat(d_there_file, "/");
	    }
	}

1025
1026
1027
1028
1029
	/* 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 */
1030
	if (last_slash == NULL)
1031
	    d_there = mallocstrcpy(d_there, d_here);
1032
	else {
1033
1034
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1035
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1036
	    null_at(&d_there, last_slash_index + 1);
1037
1038
1039

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

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1050
1051
1052
		/* 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 */
1053
1054
1055
1056
1057
1058

		free(d_there);

		d_there = getcwd(NULL, PATH_MAX + 1);

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

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

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

1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
	/* 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);
	}
1091
1092
1093
1094
1095
1096
1097
1098
1099

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

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

#ifndef DISABLE_SPELLER
/*
1104
1105
1106
 * 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.
1107
 */
1108
char *check_writable_directory(const char *path)
1109
{
1110
    char *full_path = get_full_path(path);
1111
    int writable;
1112
1113
    struct stat fileinfo;

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

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

    /* if the full path doesn't end in a slash (meaning get_full_path()
1123
1124
1125
1126
       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);
1127
	return NULL;
1128
    }
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138

    /* 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
1139
1140
1141
1142
1143
1144
1145
 * 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.
1146
 */
1147
1148
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1149
1150
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1151
    int filedesc;
1152

1153
1154
      /* 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,
1155
         leave full_tempdir set to NULL */
1156
    TMPDIR_env = getenv("TMPDIR");
1157
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1158
	full_tempdir = check_writable_directory(TMPDIR_env);
1159

1160
1161
1162
    /* 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 */
1163
    if (full_tempdir == NULL && dirname != NULL)
1164
	full_tempdir = check_writable_directory(dirname);
1165
1166
1167

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

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

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

    /* like tempnam(), use only the first 5 characters of the prefix */
1180
1181
1182
1183
1184
1185
1186
1187
    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) {
1188
	close(filedesc);
1189
1190
	unlink(full_tempdir);
	return full_tempdir;
1191
    }
1192
1193
1194

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

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

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

1207
1208
1209
    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
1210
     * inaccessible, unset operating_dir. */
1211
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1212
1213
1214
1215
1216
1217
1218
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1219
/* Check to see if we're inside the operating directory.  Return 0 if we
1220
1221
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1222
 * completion will work. */
1223
int check_operating_dir(const char *currpath, bool allow_tabcomp)
1224
{
1225
1226
1227
1228
    /* 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. */
1229

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

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

1238
    assert(full_operating_dir != NULL);
1239
1240

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

    /* 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. */
1249
    if (fullpath == NULL)
1250
	return allow_tabcomp;
1251
1252
1253
1254
1255

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

1256
1257
1258
1259
1260
    /* 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. */
1261
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1262
1263
	retval = 1;
    free(fullpath);	
1264
1265

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

1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
#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

1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
/* 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;
}

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

1367
    assert(name != NULL);
1368
    if (name[0] == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1369
	return -1;
1370
1371
    if (!tmp)
	titlebar(NULL);
1372

1373
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1374

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

1384
    anyexists = (lstat(realname, &lst) != -1);
1385

1386
1387
1388
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
1389

1390
1391
1392
    /* If NOFOLLOW_SYMLINKS is set, it doesn't make sense to prepend or
     * append to a symlink.  Here we warn about the contradiction. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode)) {
1393
1394
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1395
1396
1397
	goto cleanup_and_exit;
    }

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

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

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

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

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

1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
	/* If backup_dir is set, we set backupname to
	 * backup_dir/backupname~, where backupnae is the canonicalized
	 * absolute pathname of realname with every '/' replaced with a
	 * '!'.  This means that /home/foo/file is backed up in
	 * backup_dir/!home!foo!file~. */
	if (backup_dir != NULL) {
	    char *canon_realname = get_full_path(realname);
	    size_t i;

	    if (canon_realname == NULL)
		/* If get_full_path() failed, we don't have a
		 * canonicalized absolute pathname, so just use the
		 * filename portion of the pathname.  We use tail() so
		 * that e.g. ../backupname will be backed up in
		 * backupdir/backupname~ instead of
		 * backupdir/../backupname~. */
		canon_realname = mallocstrcpy(NULL, tail(realname));
	    else {
		for (i = 0; canon_realname[i] != '\0'; i++) {
		    if (canon_realname[i] == '/')
			canon_realname[i] = '!';
		}
	    }

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

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

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

1481
1482
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1483

1484
	/* And set metadata. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1485
1486
1487
	if (copy_status != 0 || chown(backupname,
		originalfilestat.st_uid, originalfilestat.st_gid) == -1
		|| utime(backupname, &filetime) == -1) {
1488
1489
	    free(backupname);
	    if (copy_status == -1)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1490
1491
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1492
1493
1494
1495
1496
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1497
1498
	free(backupname);
    }
1499
#endif /* !NANO_SMALL */
1500

1501
1502
1503
1504
1505
1506
    /* 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));
1507
	goto cleanup_and_exit;
1508
    }
1509

1510
1511
    original_umask = umask(0);
    umask(original_umask);
1512

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

1518
    /* If we're prepending, copy the file to a temp file. */
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
    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) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1534
1535
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1536
	    unlink(tempname);
1537
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1538
	}
1539

1540
1541
1542
1543
1544
1545
1546
	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) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1547
1548
	    statusbar(_("Error reading %s: %s"), realname,
		strerror(errno));
1549
1550
1551
1552
1553
1554
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

	if (copy_file(f_source, f) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1555
1556
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1557
	    unlink(tempname);
1558
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1559
1560
1561
	}
    }

1562
1563
    /* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This is
     * now copied from joe, because wiggy says so *shrug*. */
1564
1565
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
1566
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1567

1568
    /* Set the umask back to the user's original value. */
1569
1570
1571
1572
1573
    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));
1574
1575
1576
	/* tempname has been set only if we're prepending. */
	if (tempname != NULL)
	    unlink(tempname);
1577
1578
	goto cleanup_and_exit;
    }
1579

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1580
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1581
    if (f == NULL) {
1582
1583
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1584
	goto cleanup_and_exit;
1585
1586
    }

1587
    /* There might not be a magicline.  There won't be when writing out
1588
1589
1590
1591
1592
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1593

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

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

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

1602
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1603
1604
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1605
	    fclose(f);
1606
	    goto cleanup_and_exit;
1607
	}
1608
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1609
	if (fmt == DOS_FILE || fmt == MAC_FILE) {
1610
	    if (putc('\r', f) == EOF) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1611
1612
		statusbar(_("Error writing %s: %s"), realname,
			strerror(errno));
1613
1614
1615
		fclose(f);
		goto cleanup_and_exit;
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1616
	}
Chris Allegretta's avatar
Chris Allegretta committed
1617

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1618
	if (fmt != MAC_FILE) {
1619
#endif
1620
	    if (putc('\n', f) == EOF) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1621
1622
		statusbar(_("Error writing %s: %s"), realname,
			strerror(errno));
1623
1624
1625
		fclose(f);
		goto cleanup_and_exit;
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1626
1627
1628
#ifndef NANO_SMALL
	}
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1629
1630
1631
1632
1633

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

1634
    /* If we're prepending, open the temp file, and append it to f. */
1635
    if (append == 2) {
1636
1637
1638
1639
1640
1641
1642
1643
	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);
1644
	}
1645
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1646
1647
	    statusbar(_("Error reading %s: %s"), tempname,
		strerror(errno));
1648
	    fclose(f);
1649
	    goto cleanup_and_exit;
1650
1651
	}

1652
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1653
1654
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1655
	    goto cleanup_and_exit;
1656
	}
1657
1658
1659
1660
    } 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
1661
    }
1662

1663
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1664
	if (!nonamechange) {
1665
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1666
1667
#ifdef ENABLE_COLOR
	    update_color();
1668
1669
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1670
1671
#endif
	}
1672

1673
1674
1675
1676
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1677
1678
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1679
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1680
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1681
    }
1682
1683
1684
1685
1686

    retval = 1;

  cleanup_and_exit:
    free(realname);
1687
    free(tempname);
1688
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1689
1690
}

1691
1692
1693
#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
1694
1695
1696
1697
 * 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. */
1698
int write_marked(const char *name, bool tmp, int append)
1699
1700
{
    int retval = -1;
1701
    bool old_modified = ISSET(MODIFIED);
1702
	/* write_file() unsets the MODIFIED flag. */
1703
1704
1705
1706
1707
1708
1709
1710
    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,
1711
	(const filestruct **)&bot, &bot_x, NULL);
1712
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1713
1714

    /* If the line at filebot is blank, treat it as the magicline and
1715
1716
1717
1718
1719
     * 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();
1720

1721
    retval = write_file(name, tmp, append, TRUE);
1722

1723
1724
1725
1726
1727
1728
    /* If we added a magicline, remove it now. */
    if (added_magicline)
	remove_magicline();

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

1731
    if (old_modified)
1732
1733
1734
1735
1736
1737
	set_modified();

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

1738
int do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1739
{
1740
    int i;
1741
1742
1743
    int retval = 0, append = 0;
    char *ans;
	/* The last answer the user typed on the statusbar. */
1744
#ifdef NANO_EXTRA
1745
    static bool did_cred = FALSE;
1746
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1747

1748
    currshortcut = writefile_list;
1749

1750
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1751
1752
1753
1754
1755
	retval = write_file(filename, FALSE, 0, FALSE);

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

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

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

1770
	if (fmt == DOS_FILE)
1771
	   formatstr = N_(" [DOS Format]");
1772
	else if (fmt == MAC_FILE)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1773
	   formatstr = N_(" [Mac Format]");
1774
1775
1776
	else
	   formatstr = "";

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

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

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

1811
	if (i < 0) {
1812
	    statusbar(_("Cancelled"));
1813
1814
1815
1816
	    retval = -1;
	    break;
	} else {
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
1817

1818
#ifndef DISABLE_BROWSER
1819
1820
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
1821

1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
		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
1833
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1834
#ifndef NANO_SMALL
1835
	    if (i == TOGGLE_DOS_KEY) {
1836
		fmt = (fmt == DOS_FILE) ? NIX_FILE : DOS_FILE;
1837
1838
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1839
		fmt = (fmt == MAC_FILE) ? NIX_FILE : MAC_FILE;
1840
1841
1842
1843
1844
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1845
#endif /* !NANO_SMALL */
1846
1847
1848
1849
1850
1851
1852
	    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
1853

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

#ifdef NANO_EXTRA
1859
1860
1861
1862
1863
1864
1865
	    if (exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0 && !did_cred) {
		do_credits();
		did_cred = TRUE;
		retval = -1;
		break;
	    }
1866
#endif
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
	    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'
1880
#ifndef NANO_SMALL
1881
			&& (exiting || !ISSET(MARK_ISSET))
1882
#endif
1883
			) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1884
1885
		    i = do_yesno(FALSE,
			_("Save file under DIFFERENT NAME ? "));
1886
1887
1888
		    if (i == 0 || i == -1)
			continue;
		}
1889
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1890

1891
#ifndef NANO_SMALL
1892
1893
1894
1895
1896
1897
1898
	    /* 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
1899
#endif /* !NANO_SMALL */
1900
		retval = write_file(answer, FALSE, append, FALSE);
1901

1902
#ifdef ENABLE_MULTIBUFFER
1903
1904
1905
1906
	    /* If we're not about to exit, update the current entry in
	     * the open_files structure. */
	    if (!exiting)
		add_open_file(TRUE);
1907
#endif
1908
1909
1910

	    break;
	}
1911
    } /* while (TRUE) */
1912
1913
1914

    free(ans);
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1915
1916
}

1917
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1918
{
1919
    do_writeout(FALSE);
1920
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1921
}
Chris Allegretta's avatar
Chris Allegretta committed
1922

1923
/* Return a malloc()ed string containing the actual directory, used
1924
1925
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1926
{
Chris Allegretta's avatar
Chris Allegretta committed
1927
    char *dirtmp = NULL;
1928

1929
    if (buf[0] == '~') {
1930
1931
1932
1933
1934
1935
1936
	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++)
	    ;

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

1949
	if (userdata != NULL) {	/* User found */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1950
1951
	    dirtmp = charalloc(strlen(userdata->pw_dir) +
		strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1952
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1953
	}
1954
    }
1955

1956
1957
1958
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1959
    return dirtmp;
1960
1961
}

Chris Allegretta's avatar
Chris Allegretta committed
1962
#ifndef DISABLE_TABCOMP
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1963
1964
1965
/* 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. */
1966
int append_slash_if_dir(char *buf, bool *lastwastab, int *place)
1967
{
1968
    char *dirptr = real_dir_from_tilde(buf);
1969
    struct stat fileinfo;
1970
    int ret = 0;
1971

1972
    assert(dirptr != buf);
1973

1974
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
1975
	strncat(buf, "/", 1);
1976
	(*place)++;
1977
	/* now we start over again with # of tabs so far */
1978
	*lastwastab = FALSE;
1979
	ret = 1;
1980
1981
    }

1982
    free(dirptr);
1983
    return ret;
1984
}
Chris Allegretta's avatar
Chris Allegretta committed
1985
1986

/*
Chris Allegretta's avatar
Chris Allegretta committed
1987
1988
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1989
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
 *
 * 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)
{
2007
    char **matches = (char **)NULL;
2008
    char *matchline = NULL;
2009
    struct passwd *userdata;
2010

2011
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2012
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2013

2014
    strcat(buf, "*");
2015

2016
    while ((userdata = getpwent()) != NULL) {
2017

2018
	if (check_wildcard_match(userdata->pw_name, &buf[1])) {
2019
2020
2021
2022

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

2024
2025
2026
2027
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2028
	    if (operating_dir != NULL) {
2029
		if (check_operating_dir(userdata->pw_dir, TRUE) != 0)
2030
2031
2032
2033
		    continue;
	    }
#endif

2034
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2035
2036
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
2037
	    ++(*num_matches);
2038

2039
2040
2041
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2042
	}
2043
2044
    }
    endpwent();
2045

2046
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2047
2048
2049
2050
2051
2052
2053
}

/* 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
2054
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2055
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2056
2057
2058
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2059
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2060
2061
2062
2063

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

2064
    /* Okie, if there's a / in the buffer, strip out the directory part */
2065
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2066
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2067
2068
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2069
	    tmp--;
2070

Chris Allegretta's avatar
Chris Allegretta committed
2071
2072
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2073
2074
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2075

Chris Allegretta's avatar
Chris Allegretta committed
2076
    } else {
2077

Chris Allegretta's avatar
Chris Allegretta committed
2078
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2079
2080
2081
2082
2083
2084
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2085
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2086
2087
2088
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2089

Chris Allegretta's avatar
Chris Allegretta committed
2090
2091
2092
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2093
2094

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2095
    fprintf(stderr, "\nDir = %s\n", dirname);
2096
2097
2098
2099
2100
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2101
    dir = opendir(dirname);
2102
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2103
2104
2105
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2106
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2107
2108
2109
2110
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2111
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2112
2113
#endif
	/* See if this matches */
2114
	if (check_wildcard_match(next->d_name, tmp)) {
2115
2116
2117
2118

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2119
2120
2121
2122
2123
2124
2125
2126

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

2127
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2128
2129
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2130
2131
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2132
		if (check_operating_dir(tmp2, TRUE) != 0) {
2133
2134
2135
2136
2137
2138
2139
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2140
	    tmp2 = NULL;
2141
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2142
2143
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2144
	    ++*num_matches;
2145
2146
2147
2148

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2149
2150
	}
    }
2151
2152
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2153

2154
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2155
2156
}

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

2169
    *list = FALSE;
2170

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2171
    if (*lastwastab == FALSE) {
2172
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2173

2174
	*lastwastab = TRUE;
2175

Chris Allegretta's avatar
Chris Allegretta committed
2176
2177
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2178
2179
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2180

2181
2182
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2183
2184

	/* skip any leading white space */
2185
	while (*tmp && is_blank_char(*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2186
2187
2188
2189
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2190
2191
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2192
	    free(matches);
2193
	    matches = (char **)NULL;
2194
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2195
2196
2197
2198
2199
	}

	/* 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
2200
2201
2202
2203
2204
	/* 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. */
2205
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2206
	    buf = mallocstrcpy(buf, tmp);
2207
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2208
	}
2209
2210
2211
2212
2213
2214
	/* 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
2215
2216
2217

	/* Try to match everything in the current working directory that
	 * matches.  */
2218
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2219
2220
2221
	    matches = cwd_tab_completion(tmp, &num_matches);

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

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

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

2237
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2238
2239
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2240
		tmp++;
2241
	    } else
2242
2243
		tmp = buf;

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

2247
	    if (is_dir != 0)
2248
		break;
2249
2250

	    copyto = tmp;
2251
2252
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2253
2254
		tmp++;

2255
	    /* write out the matched name */
2256
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2257
2258
	    *newplace += strlen(matches[0]) - pos;

2259
2260
2261
2262
2263
2264
2265
	    /* 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;

2266
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2267
	    append_slash_if_dir(buf, lastwastab, newplace);
2268

2269
2270
	    break;
	default:
2271
	    /* Check to see if all matches share a beginning, and, if so,
2272
	       tack it onto buf and then beep */
2273

2274
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2275
2276
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2277
		tmp++;
2278
	    } else
2279
2280
		tmp = buf;

2281
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2282
		 pos <= strlen(matches[0]); pos++)
2283
2284
		tmp++;

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

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

2318
	    editline = 0;
2319

2320
2321
2322
2323
2324
2325
2326
2327
	    /* 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;

2328
	    foo = charalloc(longestname + 5);
2329

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2333
2334
		/* make each filename shown be the same length as the
		   longest filename, with two spaces at the end */
2335
		snprintf(foo, longestname + 1, "%s", matches[i]);
2336
2337
2338
2339
2340
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2341
2342
		/* Disable el cursor */
		curs_set(0);
2343
2344
2345
2346
2347
2348
		/* 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 */
2349
		if (col > COLS - longestname && i + 1 < num_matches) {
2350
2351
		    editline++;
		    wmove(edit, editline, 0);
2352
2353
2354
2355
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2356
2357
2358
		    col = 0;
		}
	    }
2359
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2360
	    wrefresh(edit);
2361
	    *list = TRUE;
2362
2363
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2364
2365
    }

2366
2367
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
2368
    if (*list == FALSE)
2369
	edit_refresh();
2370
2371
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2372
}
2373
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2374

2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
/* 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;
}

2390
#ifndef DISABLE_BROWSER
2391
/* Our sort routine for file listings -- sort directories before
2392
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2393
2394
int diralphasort(const void *va, const void *vb)
{
2395
2396
2397
2398
    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
2399

2400
    if (aisdir != 0 && bisdir == 0)
2401
	return -1;
2402
    if (aisdir == 0 && bisdir != 0)
2403
	return 1;
2404

2405
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2406
2407
}

2408
/* Free our malloc()ed memory */
2409
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2410
{
2411
2412
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2413
2414
2415
    free(array);
}

2416
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2417
2418
void striponedir(char *foo)
{
2419
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2420

2421
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2422
    /* Don't strip the root dir */
2423
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2424
2425
	return;

2426
2427
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2428
    if (*tmp == '/')
2429
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2430
2431
2432
2433
2434

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

    if (tmp != foo)
2435
2436
2437
2438
2439
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2440
    }
Chris Allegretta's avatar
Chris Allegretta committed
2441
2442
}

2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
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;
}

2453
/* Initialize the browser code, including the list of files in *path */
2454
char **browser_init(const char *path, int *longest, int *numents)
2455
2456
2457
{
    DIR *dir;
    struct dirent *next;
2458
    char **filelist;
2459
    int i = 0;
2460
    size_t path_len;
2461
2462

    dir = opendir(path);
2463
    if (dir == NULL)
2464
2465
2466
2467
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
2468
	if (strcmp(next->d_name, ".") == 0)
2469
2470
2471
2472
2473
2474
2475
2476
	   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
2477
    filelist = (char **)nmalloc(*numents * sizeof (char *));
2478

2479
    if (strcmp(path, "/") == 0)
2480
2481
2482
	path = "";
    path_len = strlen(path);

2483
    while ((next = readdir(dir)) != NULL) {
2484
	if (strcmp(next->d_name, ".") == 0)
2485
2486
	   continue;

2487
2488
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2489
2490
	i++;
    }
2491
    closedir(dir);
2492
2493
2494
2495
2496
2497
2498

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2499
/* Our browser function.  inpath is the path to start browsing from */
2500
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2501
2502
2503
2504
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2505
2506
2507
    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;
2508
    bool meta_key, func_key;
2509
    char **filelist = (char **)NULL;
2510
#ifndef DISABLE_MOUSE
2511
2512
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2513

2514
2515
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2516
2517
2518
    /* 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 */
2519
    if (path != NULL && strcmp(path, inpath) != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2520
2521
2522
2523
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2524
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2525
    if (path == NULL)
2526
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2527
2528

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

2531
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2532
2533
2534
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2535
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2536
2537
2538
2539
2540
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2541
2542

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2543
    do {
2544
2545
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2546

2547
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2548

2549
	currshortcut = browser_list;
2550

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

	switch (kbinput) {
2560

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

		mevent.y -= 2;

2573
2574
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2575
		selected = (lineno / editwinrows) * editwinrows * width
2576
2577
2578
2579
2580
2581
			+ 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--;
2582
2583
2584

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2585
		if (selected > numents - 1)
2586
		    selected = numents - 1;
2587
		else if (selectedbackup == selected)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2588
		    /* Put back the 'select' key */
2589
		    unget_kbinput('s', FALSE, FALSE);
2590
2591
	    } else {
		/* Must be clicking a shortcut */
2592
2593
2594
		int mouse_x, mouse_y;
		get_mouseinput(&mouse_x, &mouse_y, TRUE);
	    }
2595

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

2650
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2651
2652
2653
	    /* 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. */
2654
	    if (check_operating_dir(filelist[selected], FALSE) != 0) {
2655
2656
		statusbar(_("Can't go outside of %s in restricted mode"),
			operating_dir);
2657
2658
		beep();
		break;
2659
2660
2661
	    }
#endif

2662
	    if (stat(filelist[selected], &st) == -1) {
2663
2664
		statusbar(_("Can't open \"%s\": %s"), filelist[selected],
			strerror(errno));
2665
2666
2667
		beep();
		break;
	    }
2668

2669
2670
2671
2672
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2673
2674
	    }

2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
	    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
2685
		}
2686
2687
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2688

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

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

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

	    if (j < 0) {
2718
		statusbar(_("Cancelled"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2719
2720
2721
		break;
	    }

2722
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2723

2724
2725
2726
	    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
2727
2728
	    }

2729
#ifndef DISABLE_OPERATINGDIR
2730
	    if (check_operating_dir(new_path, FALSE) != 0) {
2731
2732
2733
2734
2735
2736
2737
		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
2738
2739
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2740
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2741
		break;
2742
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2743
2744

	    /* Start over again with the new path value */
2745
2746
	    free_charptrarray(filelist, numents);
	    free(foo);
2747
2748
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2749
2750
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2751
	/* Stuff we want to abort the browser */
2752
	case NANO_CANCEL_KEY:
2753
	case NANO_EXIT_KEY:
2754
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2755
2756
	case 'E': /* Pico compatibility */
	case 'e':
2757
2758
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2759
2760
2761
2762
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2763
2764
	blank_edit();

2765
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
	    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 */
2780
2781
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2782
2783
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2784
2785
2786
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2787
2788
2789
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
2790
		    stat(filelist[j], &st);
2791
2792
2793
2794
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2795
2796
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2797
			(int) st.st_size);
2798
2799
2800
2801
2802
2803
		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);
2804
		else /* It's more than 1 k and less than a meg */
2805
2806
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2807
2808
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2809
	    /* Highlight the currently selected file/dir */
2810
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2811
		wattron(edit, A_REVERSE);
2812
2813
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2814
2815
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2816
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2817
2818
2819
2820
2821
	    waddstr(edit, "  ");
	    col += 2;

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

Chris Allegretta's avatar
Chris Allegretta committed
2838
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2839
2840
2841
2842
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2843

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

2854
2855
2856
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2857

2858
2859
2860
2861
2862
2863
2864
2865
    /*
     * 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);
2866
2867
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2868
	    path = getcwd(NULL, PATH_MAX + 1);
2869
	}
2870
    }
2871

2872
2873
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2874
    if (check_operating_dir(path, FALSE) != 0)
2875
2876
2877
2878
2879
2880
2881
2882
2883
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2884
    return bob;
2885
}
Chris Allegretta's avatar
Chris Allegretta committed
2886
#endif /* !DISABLE_BROWSER */
2887

2888
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2889
2890
2891
/* 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)
2892
{
2893
    char *nanohist = NULL;
2894

2895
2896
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2897

2898
2899
2900
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2901
    }
2902
2903
2904
2905
2906
2907
    return nanohist;
}

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

2909
    /* assume do_rcfile() has reported missing home dir */
2910
2911
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2912

2913
	if (hist == NULL) {
2914
	    if (errno != ENOENT) {
2915
2916
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2917
		rcfile_error(N_("Error reading %s: %s"), nanohist, strerror(errno));
2918
2919
2920
		fprintf(stderr, _("\nPress Return to continue starting nano\n"));
		while (getchar() != '\n')
		    ;
2921
	    }
2922
	} else {
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
	    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
2937
2938
2939
		    history = &replace_history;
	    }
	    fclose(hist);
2940
	    free(line);
2941
2942
	    UNSET(HISTORY_CHANGED);
	}
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
	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;
2959
    }
2960
    return TRUE;
2961
2962
2963
2964
2965
}

/* save histories to ~/.nano_history */
void save_history(void)
{
2966
    char *nanohist;
2967
2968

    /* don't save unchanged or empty histories */
2969
    if ((search_history.count == 0 && replace_history.count == 0) ||
2970
	!ISSET(HISTORY_CHANGED) || ISSET(VIEW_MODE))
2971
2972
	return;

2973
2974
2975
2976
    nanohist = histfilename();

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

2978
	if (hist == NULL)
2979
	    rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2980
	else {
2981
2982
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2983
2984
2985
2986

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2987
		rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2988
2989
2990
2991
2992
	    fclose(hist);
	}
	free(nanohist);
    }
}
2993
#endif /* !NANO_SMALL && ENABLE_NANORC */