files.c 79.2 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
/**************************************************************************
 *   files.c                                                              *
 *                                                                        *
5
 *   Copyright (C) 1999-2004 Chris Allegretta                             *
Chris Allegretta's avatar
Chris Allegretta committed
6
7
 *   This program is free software; you can redistribute it and/or modify *
 *   it under the terms of the GNU General Public License as published by *
8
 *   the Free Software Foundation; either version 2, or (at your option)  *
Chris Allegretta's avatar
Chris Allegretta committed
9
10
11
12
13
14
15
16
17
18
19
20
21
 *   any later version.                                                   *
 *                                                                        *
 *   This program is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of       *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
 *   GNU General Public License for more details.                         *
 *                                                                        *
 *   You should have received a copy of the GNU General Public License    *
 *   along with this program; if not, write to the Free Software          *
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            *
 *                                                                        *
 **************************************************************************/

22
23
#include "config.h"

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

481
482
483
484
485
486
487
void do_insertfile(
#ifndef NANO_SMALL
	bool execute
#else
	void
#endif
	)
488
489
490
{
    int i;
    const char *msg;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
491
    char *ans = mallocstrcpy(NULL, "");
492
	/* The last answer the user typed on the statusbar. */
493

494
#ifndef DISABLE_WRAPPING
495
    wrap_reset();
496
#endif
497

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

519
	i = statusq(TRUE,
520
#ifndef NANO_SMALL
521
		execute ? extcmd_list :
522
523
#endif
		insertfile_list, ans,
Chris Allegretta's avatar
Chris Allegretta committed
524
#ifndef NANO_SMALL
525
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
526
#endif
527
528
529
530
		_(msg),
#ifndef DISABLE_OPERATINGDIR
		operating_dir != NULL && strcmp(operating_dir, ".") != 0 ?
		operating_dir :
531
#endif
532
		"./");
533

534
535
536
537
	if (i < 0) {
	    statusbar(_("Cancelled"));
	    break;
	} else {
538
539
	    size_t old_current_x = current_x;
	    size_t old_pww = placewewant;
540

541
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
542

543
#if !defined(NANO_SMALL) && defined(ENABLE_MULTIBUFFER)
544
545
546
547
548
549
	    if (i == TOGGLE_MULTIBUFFER_KEY) {
		/* Don't allow toggling if we're in view mode. */
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
		continue;
	    }
550
#endif
551

552
#ifndef DISABLE_BROWSER
553
554
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
555

556
557
		if (tmp == NULL)
		    continue;
558
559
		free(answer);
		answer = tmp;
560
561
562
563

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

567
#ifndef NANO_SMALL
568
569
570
571
	    if (i == NANO_TOOTHERINSERT_KEY) {
		execute = !execute;
		continue;
	    }
572

573
574
575
	    if (execute)
		execute_command(answer);
	    else {
576
#endif
577
578
		answer = mallocstrassn(answer, real_dir_from_tilde(answer));
		load_buffer(answer);
579
#ifndef NANO_SMALL
580
	    }
581
#endif
582

583
#ifdef ENABLE_MULTIBUFFER
584
585
586
	    if (ISSET(MULTIBUFFER)) {
		/* Update the titlebar. */
		titlebar(NULL);
587

588
589
590
		/* Reinitialize the shortcut list. */
		shortcut_init(FALSE);
	    } else {
591
#endif
592
593
		/* Mark the file as modified. */
		set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
594

595
		/* Restore the old cursor position. */
596
		current_x = old_current_x;
597
		placewewant = old_pww;
598
#ifdef ENABLE_MULTIBUFFER
599
	    }
Chris Allegretta's avatar
Chris Allegretta committed
600
601
#endif

602
603
604
605
606
607
	    /* Refresh the screen. */
	    edit_refresh();

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
609
    free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
610
611
}

612
void do_insertfile_void(void)
613
{
614
#ifdef ENABLE_MULTIBUFFER
615
616
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
	statusbar(_("Key illegal in non-multibuffer mode"));
617
    else
618
#endif
619
620
621
622
623
	do_insertfile(
#ifndef NANO_SMALL
		FALSE
#endif
		);
624
625
626
627

    display_main_list();
}

628
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
629
630
631
/* Create a new openfilestruct node. */
openfilestruct *make_new_opennode(openfilestruct *prevnode)
{
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
632
    openfilestruct *newnode = (openfilestruct *)nmalloc(sizeof(openfilestruct));
Chris Allegretta's avatar
Chris Allegretta committed
633
634
635
636
637
638
639
640
641
642
643
644
645

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

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

    return newnode;
}

/* Splice a node into an existing openfilestruct. */
void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
646
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
{
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

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

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

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

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

/* Deallocate all memory associated with this and later files,
 * including the lines of text. */
void free_openfilestruct(openfilestruct *src)
{
    if (src != NULL) {
	while (src->next != NULL) {
	    src = src->next;
	    delete_opennode(src->prev);
#ifdef DEBUG
688
	    fprintf(stderr, "%s: free'd a node, YAY!\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
689
690
691
692
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
693
	fprintf(stderr, "%s: free'd last node.\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
694
695
696
697
#endif
    }
}

698
/*
699
 * Add/update an entry to the open_files openfilestruct.  If update is
700
701
 * FALSE, a new entry is created; otherwise, the current entry is
 * updated.
702
 */
703
void add_open_file(bool update)
704
{
705
    openfilestruct *tmp;
706

707
    if (fileage == NULL || current == NULL || filename == NULL)
708
	return;
709
710

    /* if no entries, make the first one */
711
    if (open_files == NULL)
712
	open_files = make_new_opennode(NULL);
713
714
715
716
717
718
719

    else if (!update) {

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

#ifdef DEBUG
720
	fprintf(stderr, "filename is %s\n", open_files->filename);
721
722
#endif

723
724
	tmp = make_new_opennode(NULL);
	splice_opennode(open_files, tmp, open_files->next);
725
726
727
728
	open_files = open_files->next;
    }

    /* save current filename */
729
    open_files->filename = mallocstrcpy(open_files->filename, filename);
730

731
732
733
734
735
#ifndef NANO_SMALL
    /* save the file's stat */
    open_files->originalfilestat = originalfilestat;
#endif

736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
    /* save current total number of lines */
    open_files->file_totlines = totlines;

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

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

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

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

    /* save current line number */
752
    open_files->file_lineno = current->lineno;
753

754
755
756
    /* start with default modification status: unmodified, unmarked (if
       available), not in DOS format (if available), and not in Mac
       format (if available) */
757
758
    open_files->file_flags = 0;

759
760
761
    /* if we're updating, save current modification status, current
       marking status (if available), and current file format status (if
       available) */
Chris Allegretta's avatar
Chris Allegretta committed
762
    if (update) {
763
764
	if (ISSET(MODIFIED))
	    open_files->file_flags |= MODIFIED;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
765
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
766
767
768
	if (ISSET(MARK_ISSET)) {
	    open_files->file_mark_beginbuf = mark_beginbuf;
	    open_files->file_mark_beginx = mark_beginx;
769
	    open_files->file_flags |= MARK_ISSET;
Chris Allegretta's avatar
Chris Allegretta committed
770
	}
771
	open_files->file_fmt = fmt;
Chris Allegretta's avatar
Chris Allegretta committed
772
773
774
#endif
    }

775
776
777
    /* if we're not in view mode and not updating, the file contents
       might have changed, so save the filestruct; otherwise, don't */
    if (!(ISSET(VIEW_MODE) && !update)) {
Chris Allegretta's avatar
Chris Allegretta committed
778
779
780
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
781
    }
782
783

#ifdef DEBUG
784
    fprintf(stderr, "filename is %s\n", open_files->filename);
785
786
787
788
789
#endif
}

/*
 * Read the current entry in the open_files structure and set up the
790
 * currently open file using that entry's information.
791
 */
792
void load_open_file(void)
793
{
794
    if (open_files == NULL)
795
	return;
796
797
798

    /* set up the filename, the file buffer, the total number of lines in
       the file, and the total file size */
799
    filename = mallocstrcpy(filename, open_files->filename);
800
801
802
#ifndef NANO_SMALL
    originalfilestat = open_files->originalfilestat;
#endif
803
    fileage = open_files->fileage;
804
    current = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
805
    filebot = open_files->filebot;
806
807
808
    totlines = open_files->file_totlines;
    totsize = open_files->file_totsize;

Chris Allegretta's avatar
Chris Allegretta committed
809
810
811
812
813
814
815
816
817
818
819
820
821
822
    /* restore modification status */
    if (open_files->file_flags & MODIFIED)
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

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

    /* restore file format status */
825
    fmt = open_files->file_fmt;
Chris Allegretta's avatar
Chris Allegretta committed
826
#endif
827

Chris Allegretta's avatar
Chris Allegretta committed
828
829
830
831
#ifdef ENABLE_COLOR
    update_color();
#endif

832
833
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
834
835
    do_gotopos(open_files->file_lineno, open_files->file_current_x,
	open_files->file_current_y, open_files->file_placewewant);
836

Chris Allegretta's avatar
Chris Allegretta committed
837
    /* update the titlebar */
838
839
840
841
842
843
    clearok(topwin, FALSE);
    titlebar(NULL);
}

/*
 * Open the previous entry in the open_files structure.  If closing_file
844
845
846
 * is FALSE, update the current entry before switching from it.
 * Otherwise, we are about to close that entry, so don't bother doing
 * so.
847
 */
848
void open_prevfile(bool closing_file)
849
{
850
    if (open_files == NULL)
851
	return;
852
853

    /* if we're not about to close the current entry, update it before
854
       doing anything */
855
    if (!closing_file)
856
	add_open_file(TRUE);
857

858
    if (open_files->prev == NULL && open_files->next == NULL) {
859
860
861

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
862
	    statusbar(_("No more open file buffers"));
863
	return;
864
865
    }

866
    if (open_files->prev != NULL) {
867
868
869
	open_files = open_files->prev;

#ifdef DEBUG
870
	fprintf(stderr, "filename is %s\n", open_files->filename);
871
872
873
874
#endif

    }

875
    else if (open_files->next != NULL) {
876
877

	/* if we're at the beginning, wrap around to the end */
878
	while (open_files->next != NULL)
879
880
881
	    open_files = open_files->next;

#ifdef DEBUG
882
	    fprintf(stderr, "filename is %s\n", open_files->filename);
883
884
885
886
887
888
#endif

    }

    load_open_file();

889
    statusbar(_("Switched to %s"),
890
      ((open_files->filename[0] == '\0') ? _("New Buffer") :
891
	open_files->filename));
892

893
894
895
896
897
#ifdef DEBUG
    dump_buffer(current);
#endif
}

898
void open_prevfile_void(void)
899
{
900
    open_prevfile(FALSE);
901
902
}

903
904
/*
 * Open the next entry in the open_files structure.  If closing_file is
905
906
 * FALSE, update the current entry before switching from it.  Otherwise,
 * we are about to close that entry, so don't bother doing so.
907
 */
908
void open_nextfile(bool closing_file)
909
{
910
    if (open_files == NULL)
911
	return;
912
913

    /* if we're not about to close the current entry, update it before
914
       doing anything */
915
    if (!closing_file)
916
	add_open_file(TRUE);
917

918
    if (open_files->prev == NULL && open_files->next == NULL) {
919
920
921

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
922
	    statusbar(_("No more open file buffers"));
923
	return;
924
925
    }

926
    if (open_files->next != NULL) {
927
928
929
	open_files = open_files->next;

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

    }
934
    else if (open_files->prev != NULL) {
935
936

	/* if we're at the end, wrap around to the beginning */
937
	while (open_files->prev != NULL) {
938
939
940
	    open_files = open_files->prev;

#ifdef DEBUG
941
	    fprintf(stderr, "filename is %s\n", open_files->filename);
942
943
944
945
946
947
948
#endif

	}
    }

    load_open_file();

949
    statusbar(_("Switched to %s"),
950
      ((open_files->filename[0] == '\0') ? _("New Buffer") :
951
	open_files->filename));
952

953
954
955
956
957
#ifdef DEBUG
    dump_buffer(current);
#endif
}

958
void open_nextfile_void(void)
959
{
960
    open_nextfile(FALSE);
961
962
}

963
964
/*
 * Delete an entry from the open_files filestruct.  After deletion of an
965
 * entry, the next or previous entry is opened, whichever is found first.
966
 * Return TRUE on success or FALSE on error.
967
 */
968
bool close_open_file(void)
969
{
970
    openfilestruct *tmp;
971

972
    if (open_files == NULL)
973
	return FALSE;
974

Chris Allegretta's avatar
Chris Allegretta committed
975
976
977
    /* make sure open_files->fileage and fileage, and open_files->filebot
       and filebot, are in sync; they might not be if lines have been cut
       from the top or bottom of the file */
978
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
979
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
980

981
    tmp = open_files;
982
983
984
985
986
    if (open_files->next != NULL)
	open_nextfile(TRUE);
    else if (open_files->prev != NULL)
	open_prevfile(TRUE);
    else
987
	return FALSE;
988

989
990
    unlink_opennode(tmp);
    delete_opennode(tmp);
991

992
    shortcut_init(FALSE);
993
    display_main_list();
994
    return TRUE;
995
}
996
#endif /* ENABLE_MULTIBUFFER */
997

998
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR) || !defined(NANO_SMALL)
999
/*
1000
1001
1002
1003
1004
1005
 * 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).
1006
 */
1007
char *get_full_path(const char *origpath)
1008
{
1009
1010
1011
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
1012
    char *expanded_origpath;
1013

1014
    /* first, get the current directory, and tack a slash onto the end of
1015
       it, unless it turns out to be "/", in which case leave it alone */
1016
1017
1018

    d_here = getcwd(NULL, PATH_MAX + 1);

1019
    if (d_here != NULL) {
1020
1021

	align(&d_here);
1022
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1023
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1024
1025
	    strcat(d_here, "/");
	}
1026
1027
1028
1029
1030

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

1033
	expanded_origpath = real_dir_from_tilde(origpath);
1034
	/* save the value of origpath in both d_there and d_there_file */
1035
1036
1037
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1038

1039
1040
1041
1042
1043
1044
	/* 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
1045
		d_there = charealloc(d_there, strlen(d_there) + 2);
1046
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1047
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1048
1049
1050
1051
		strcat(d_there_file, "/");
	    }
	}

1052
1053
1054
1055
1056
	/* 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 */
1057
	if (last_slash == NULL)
1058
	    d_there = mallocstrcpy(d_there, d_here);
1059
	else {
1060
1061
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1062
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1063
	    null_at(&d_there, last_slash_index + 1);
1064
1065
1066

	    /* and remove all non-file elements from d_there_file (i. e.
	       everything before and including the last slash); if we
1067
	       have a path but no filename, don't do anything */
1068
1069
1070
1071
1072
1073
	    if (!path_only) {
		last_slash = strrchr(d_there_file, '/');
		last_slash++;
		strcpy(d_there_file, last_slash);
		align(&d_there_file);
	    }
1074
1075
1076

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1077
1078
1079
		/* 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 */
1080
1081
1082
1083
1084
1085

		free(d_there);

		d_there = getcwd(NULL, PATH_MAX + 1);

		align(&d_there);
1086
		if (d_there != NULL) {
1087
1088
1089

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
1090
		    if (strcmp(d_there, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1091
			d_there = charealloc(d_there, strlen(d_there) + 2);
1092
1093
			strcat(d_there, "/");
		    }
1094
1095
1096
		}
		else
		    return NULL;
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
	    }

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

1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
	/* 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);
	}
1118
1119
1120
1121
1122
1123
1124
1125
1126

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

    return newpath;
}
1127
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1128
1129
1130

#ifndef DISABLE_SPELLER
/*
1131
1132
1133
 * 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.
1134
 */
1135
char *check_writable_directory(const char *path)
1136
{
1137
    char *full_path = get_full_path(path);
1138
    int writable;
1139
1140
    struct stat fileinfo;

1141
    /* if get_full_path() failed, return NULL */
1142
    if (full_path == NULL)
1143
	return NULL;
1144
1145
1146

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

    /* if the full path doesn't end in a slash (meaning get_full_path()
1150
1151
1152
1153
       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);
1154
	return NULL;
1155
    }
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165

    /* 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
1166
1167
1168
1169
1170
1171
1172
 * 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.
1173
 */
1174
1175
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1176
1177
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1178
    int filedesc;
1179

1180
1181
      /* 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,
1182
         leave full_tempdir set to NULL */
1183
    TMPDIR_env = getenv("TMPDIR");
1184
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1185
	full_tempdir = check_writable_directory(TMPDIR_env);
1186

1187
1188
1189
    /* 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 */
1190
    if (full_tempdir == NULL && dirname != NULL)
1191
	full_tempdir = check_writable_directory(dirname);
1192
1193
1194

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1195
    if (full_tempdir == NULL)
1196
	full_tempdir = check_writable_directory(P_tmpdir);
1197
1198

    /* if P_tmpdir didn't work, use /tmp instead */
1199
    if (full_tempdir == NULL) {
1200
1201
1202
1203
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1204
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1205
1206

    /* like tempnam(), use only the first 5 characters of the prefix */
1207
1208
1209
1210
1211
1212
1213
1214
    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) {
1215
	close(filedesc);
1216
1217
	unlink(full_tempdir);
	return full_tempdir;
1218
    }
1219
1220
1221

    free(full_tempdir);
    return NULL;
1222
1223
}
#endif /* !DISABLE_SPELLER */
1224
1225

#ifndef DISABLE_OPERATINGDIR
1226
1227
1228
1229
1230
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1231
    if (operating_dir == NULL)
1232
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1233

1234
1235
1236
    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
1237
     * inaccessible, unset operating_dir. */
1238
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1239
1240
1241
1242
1243
1244
1245
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1246
/* Check to see if we're inside the operating directory.  Return 0 if we
1247
1248
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1249
 * completion will work. */
1250
int check_operating_dir(const char *currpath, int allow_tabcomp)
1251
{
1252
1253
1254
1255
    /* 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. */
1256

1257
1258
1259
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1260

1261
    /* If no operating directory is set, don't bother doing anything. */
1262
    if (operating_dir == NULL)
1263
	return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1264

1265
    assert(full_operating_dir != NULL);
1266
1267

    fullpath = get_full_path(currpath);
1268
1269
1270
1271
1272
1273
1274
1275

    /* 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. */
1276
    if (fullpath == NULL)
1277
	return allow_tabcomp;
1278
1279
1280
1281
1282

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

1283
1284
1285
1286
1287
    /* 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. */
1288
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1289
1290
	retval = 1;
    free(fullpath);	
1291
1292

    /* Otherwise, we're still inside it. */
1293
    return retval;
1294
}
1295
1296
#endif

1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
#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

1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
/* 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;
}

1349
/* Write a file out.  If tmp is FALSE, we set the umask to disallow
1350
1351
1352
 * 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
1353
 *
1354
1355
 * 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.
1356
 *
1357
1358
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1359
 *
1360
1361
 * nonamechange means don't change the current filename.  It is ignored
 * if tmp is FALSE or if we're appending/prepending.
1362
1363
 *
 * Return -1 on error, 1 on success. */
1364
int write_file(const char *name, int tmp, int append, int nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1365
{
1366
1367
    int retval = -1;
	/* Instead of returning in this function, you should always
1368
1369
1370
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
    const filestruct *fileptr = fileage;
1371
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1372
	/* The file descriptor we use. */
1373
    mode_t original_umask = 0;
1374
1375
	/* Our umask, from when nano started. */
    int realexists;
1376
	/* The result of stat().  TRUE if the file exists, FALSE
1377
	 * otherwise.  If name is a link that points nowhere, realexists
1378
	 * is FALSE. */
1379
1380
1381
    struct stat st;
	/* The status fields filled in by stat(). */
    int anyexists;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1382
	/* The result of lstat().  Same as realexists unless name is a
1383
1384
1385
1386
	 * link. */
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1387
	/* name after tilde expansion. */
1388
1389
1390
1391
    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
1392

1393
    assert(name != NULL);
1394
    if (name[0] == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1395
	return -1;
1396
1397
    if (!tmp)
	titlebar(NULL);
1398

1399
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1400

1401
#ifndef DISABLE_OPERATINGDIR
1402
    /* If we're writing a temporary file, we're probably going outside
1403
     * the operating directory, so skip the operating directory test. */
1404
    if (!tmp && check_operating_dir(realname, FALSE) != 0) {
1405
1406
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1407
1408
1409
    }
#endif

1410
1411
1412
1413
1414
1415
1416
    anyexists = lstat(realname, &lst) != -1;
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
    /* If NOFOLLOW_SYMLINKS is set, it doesn't make sense to prepend or
     * append to a symlink.  Here we warn about the contradiction. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode)) {
1417
1418
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1419
1420
1421
	goto cleanup_and_exit;
    }

1422
    /* Save the state of file at the end of the symlink (if there is
1423
1424
     * one). */
    realexists = stat(realname, &st) != -1;
1425

1426
1427
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1428
1429
1430
1431
1432
1433
1434
1435
     * temporary, and the file already exists.  Furthermore, if we
     * aren't appending, prepending, or writing a selection, we backup
     * only if the file has not been modified by someone else since nano
     * opened it. */
    if (ISSET(BACKUP_FILE) && !tmp && realexists != 0 &&
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1436
	FILE *backup_file;
1437
	char *backupname;
1438
	struct utimbuf filetime;
1439
	int copy_status;
1440

1441
	/* Save the original file's access and modification times. */
1442
1443
1444
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1445
	/* Open the original file to copy to the backup. */
1446
	f = fopen(realname, "rb");
1447
	if (f == NULL) {
1448
	    statusbar(_("Error reading %s: %s"), realname,
1449
		strerror(errno));
1450
	    goto cleanup_and_exit;
1451
1452
	}

1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
	/* 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);
	}
1485

1486
1487
1488
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1489
	backup_file = fopen(backupname, "wb");
1490
1491
1492
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
	    statusbar(_("Error writing %s: %s"), backupname, strerror(errno));
1493
	    free(backupname);
1494
1495
1496
1497
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1498
1499
1500
	}

#ifdef DEBUG
1501
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1502
1503
#endif

1504
1505
1506
1507
1508
1509
1510
1511
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
	/* And set metadata. */
	if (copy_status != 0 || chown(backupname, originalfilestat.st_uid,
		originalfilestat.st_gid) == -1 ||
		utime(backupname, &filetime) == -1) {
	    free(backupname);
	    if (copy_status == -1)
1512
		statusbar(_("Error reading %s: %s"), realname, strerror(errno));
1513
1514
1515
1516
1517
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1518
1519
	free(backupname);
    }
1520
#endif /* !NANO_SMALL */
1521

1522
1523
1524
1525
1526
1527
    /* 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));
1528
	goto cleanup_and_exit;
1529
    }
1530

1531
1532
    original_umask = umask(0);
    umask(original_umask);
1533

1534
    /* If we create a temp file, we don't let anyone else access it.  We
1535
     * create a temp file if tmp is TRUE or if we're prepending. */
1536
1537
1538
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

1539
    /* If we're prepending, copy the file to a temp file. */
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
    if (append == 2) {
	int fd_source;
	FILE *f_source = NULL;

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

1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
	fd_source = open(realname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
	}
	if (f_source == NULL) {
	    statusbar(_("Error reading %s: %s"), realname, strerror(errno));
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

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

1580
1581
    /* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This is
     * now copied from joe, because wiggy says so *shrug*. */
1582
1583
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
1584
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1585

1586
    /* Set the umask back to the user's original value. */
1587
1588
1589
1590
1591
    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));
1592
1593
1594
	/* tempname has been set only if we're prepending. */
	if (tempname != NULL)
	    unlink(tempname);
1595
1596
	goto cleanup_and_exit;
    }
1597

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1598
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1599
    if (f == NULL) {
1600
1601
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1602
	goto cleanup_and_exit;
1603
1604
    }

1605
    /* There might not be a magicline.  There won't be when writing out
1606
1607
1608
1609
1610
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1611

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

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

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

1620
	if (size < data_len) {
1621
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1622
	    fclose(f);
1623
	    goto cleanup_and_exit;
1624
	}
1625
#ifndef NANO_SMALL
1626
	if (fmt == DOS_FILE || fmt == MAC_FILE)
1627
1628
1629
1630
1631
	    if (putc('\r', f) == EOF) {
		statusbar(_("Error writing %s: %s"), realname, strerror(errno));
		fclose(f);
		goto cleanup_and_exit;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1632

1633
	if (fmt != MAC_FILE)
1634
#endif
1635
1636
1637
1638
1639
	    if (putc('\n', f) == EOF) {
		statusbar(_("Error writing %s: %s"), realname, strerror(errno));
		fclose(f);
		goto cleanup_and_exit;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1640
1641
1642
1643
1644

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

1645
    /* If we're prepending, open the temp file, and append it to f. */
1646
    if (append == 2) {
1647
1648
1649
1650
1651
1652
1653
1654
	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);
1655
	}
1656
	if (f_source == NULL) {
1657
1658
	    statusbar(_("Error reading %s: %s"), tempname, strerror(errno));
	    fclose(f);
1659
	    goto cleanup_and_exit;
1660
1661
	}

1662
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
1663
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1664
	    goto cleanup_and_exit;
1665
	}
1666
1667
1668
1669
    } 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
1670
    }
1671

1672
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1673
	if (!nonamechange) {
1674
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1675
1676
#ifdef ENABLE_COLOR
	    update_color();
1677
1678
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1679
1680
#endif
	}
1681

1682
1683
1684
1685
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1686
1687
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1688
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1689
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1690
    }
1691
1692
1693
1694
1695

    retval = 1;

  cleanup_and_exit:
    free(realname);
1696
    free(tempname);
1697
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1698
1699
}

1700
1701
1702
#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
1703
1704
1705
1706
1707
 * write_file() with the values of name, temp, and append, and with
 * nonamechange set to TRUE so that we don't change the current
 * filename.  Finally, set fileage and filebot back to their old values
 * and return. */
int write_marked(const char *name, int tmp, int append)
1708
1709
{
    int retval = -1;
1710
    bool old_modified = ISSET(MODIFIED);
1711
	/* write_file() unsets the MODIFIED flag. */
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
    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,
	(const filestruct **)&bot, &bot_x);
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1722
1723

    /* If the line at filebot is blank, treat it as the magicline and
1724
1725
1726
1727
1728
     * 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();
1729

1730
    retval = write_file(name, tmp, append, TRUE);
1731

1732
1733
1734
1735
1736
1737
1738
1739
    /* If we added a magicline, remove it now. */
    if (added_magicline)
	remove_magicline();

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

1740
    if (old_modified)
1741
1742
1743
1744
1745
1746
	set_modified();

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

1747
int do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1748
{
1749
    int i;
1750
1751
1752
    int retval = 0, append = 0;
    char *ans;
	/* The last answer the user typed on the statusbar. */
1753
#ifdef NANO_EXTRA
1754
    static bool did_cred = FALSE;
1755
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1756

1757
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1758
    currshortcut = writefile_list;
1759
1760
#endif

1761
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1762
1763
1764
1765
1766
	retval = write_file(filename, FALSE, 0, FALSE);

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

1769
#ifndef NANO_SMALL
1770
    if (ISSET(MARK_ISSET) && !exiting)
1771
	ans = mallocstrcpy(NULL, "");
1772
1773
    else
#endif
1774
	ans = mallocstrcpy(NULL, filename);
1775
1776
1777
1778

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

1781
	if (fmt == DOS_FILE)
1782
	   formatstr = N_(" [DOS Format]");
1783
	else if (fmt == MAC_FILE)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1784
	   formatstr = N_(" [Mac Format]");
1785
1786
1787
	else
	   formatstr = "";

1788
	if (ISSET(BACKUP_FILE))
1789
	   backupstr = N_(" [Backup]");
1790
1791
1792
	else
	   backupstr = "";

1793
	/* Be nice to the translation folks. */
1794
	if (ISSET(MARK_ISSET) && !exiting) {
1795
	    if (append == 2)
1796
		msg = N_("Prepend Selection to File");
Chris Allegretta's avatar
Chris Allegretta committed
1797
	    else if (append == 1)
1798
		msg = N_("Append Selection to File");
1799
	    else
1800
		msg = N_("Write Selection to File");
1801
1802
	} else
#endif /* !NANO_SMALL */
1803
	if (append == 2)
1804
	    msg = N_("File Name to Prepend to");
Chris Allegretta's avatar
Chris Allegretta committed
1805
	else if (append == 1)
1806
	    msg = N_("File Name to Append to");
1807
	else
1808
	    msg = N_("File Name to Write");
1809

1810
1811
1812
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
1813
	i = statusq(!ISSET(RESTRICTED) || filename[0] == '\0',
1814
		writefile_list, ans,
1815
#ifndef NANO_SMALL
1816
		NULL, "%s%s%s", _(msg), formatstr, backupstr
1817
#else
1818
		"%s", _(msg)
1819
1820
1821
#endif
		);

1822
	if (i < 0) {
1823
	    statusbar(_("Cancelled"));
1824
1825
1826
1827
	    retval = -1;
	    break;
	} else {
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
1828

1829
#ifndef DISABLE_BROWSER
1830
1831
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
1832

1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
		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
1844
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1845
#ifndef NANO_SMALL
1846
	    if (i == TOGGLE_DOS_KEY) {
1847
		fmt = (fmt == DOS_FILE) ? NIX_FILE : DOS_FILE;
1848
1849
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1850
		fmt = (fmt == MAC_FILE) ? NIX_FILE : MAC_FILE;
1851
1852
1853
1854
1855
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1856
#endif /* !NANO_SMALL */
1857
1858
1859
1860
1861
1862
1863
	    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
1864

Chris Allegretta's avatar
Chris Allegretta committed
1865
#ifdef DEBUG
1866
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1867
#endif
1868
1869

#ifdef NANO_EXTRA
1870
1871
1872
1873
1874
1875
1876
	    if (exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0 && !did_cred) {
		do_credits();
		did_cred = TRUE;
		retval = -1;
		break;
	    }
1877
#endif
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
	    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'
1891
#ifndef NANO_SMALL
1892
			&& (exiting || !ISSET(MARK_ISSET))
1893
#endif
1894
1895
1896
1897
1898
			) {
		    i = do_yesno(FALSE, _("Save file under DIFFERENT NAME ? "));
		    if (i == 0 || i == -1)
			continue;
		}
1899
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1900

1901
#ifndef NANO_SMALL
1902
1903
1904
1905
1906
1907
1908
	    /* 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
1909
#endif /* !NANO_SMALL */
1910
		retval = write_file(answer, FALSE, append, FALSE);
1911

1912
#ifdef ENABLE_MULTIBUFFER
1913
1914
1915
1916
	    /* If we're not about to exit, update the current entry in
	     * the open_files structure. */
	    if (!exiting)
		add_open_file(TRUE);
1917
#endif
1918
1919
1920

	    break;
	}
1921
    } /* while (TRUE) */
1922
1923
1924

    free(ans);
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1925
1926
}

1927
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1928
{
1929
    do_writeout(FALSE);
1930
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1931
}
Chris Allegretta's avatar
Chris Allegretta committed
1932

1933
/* Return a malloc()ed string containing the actual directory, used
1934
1935
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1936
{
Chris Allegretta's avatar
Chris Allegretta committed
1937
    char *dirtmp = NULL;
1938

1939
    if (buf[0] == '~') {
1940
1941
1942
1943
1944
1945
1946
	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++)
	    ;

1947
1948
	/* Determine home directory using getpwuid() or getpwent(),
	   don't rely on $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
1949
1950
1951
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
1952
1953
1954
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
1955
			strncmp(userdata->pw_name, buf + 1, i - 1) != 0);
Chris Allegretta's avatar
Chris Allegretta committed
1956
1957
	}
	endpwent();
1958

1959
	if (userdata != NULL) {	/* User found */
1960
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1961
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1962
	}
1963
    }
1964

1965
1966
1967
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1968
    return dirtmp;
1969
1970
}

Chris Allegretta's avatar
Chris Allegretta committed
1971
#ifndef DISABLE_TABCOMP
1972
1973
1974
/* 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. */
1975
int append_slash_if_dir(char *buf, bool *lastwastab, int *place)
1976
{
1977
    char *dirptr = real_dir_from_tilde(buf);
1978
    struct stat fileinfo;
1979
    int ret = 0;
1980

1981
    assert(dirptr != buf);
1982

1983
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
1984
	strncat(buf, "/", 1);
1985
	(*place)++;
1986
	/* now we start over again with # of tabs so far */
1987
	*lastwastab = FALSE;
1988
	ret = 1;
1989
1990
    }

1991
    free(dirptr);
1992
    return ret;
1993
}
Chris Allegretta's avatar
Chris Allegretta committed
1994
1995

/*
Chris Allegretta's avatar
Chris Allegretta committed
1996
1997
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1998
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
 *
 * 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)
{
2016
    char **matches = (char **)NULL;
2017
    char *matchline = NULL;
2018
    struct passwd *userdata;
2019

2020
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2021
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2022

2023
    strcat(buf, "*");
2024

2025
    while ((userdata = getpwent()) != NULL) {
2026

2027
	if (check_wildcard_match(userdata->pw_name, &buf[1])) {
2028
2029
2030
2031

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

2033
2034
2035
2036
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2037
	    if (operating_dir != NULL) {
2038
		if (check_operating_dir(userdata->pw_dir, TRUE) != 0)
2039
2040
2041
2042
		    continue;
	    }
#endif

2043
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2044
2045
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
2046
	    ++(*num_matches);
2047

2048
2049
2050
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2051
	}
2052
2053
    }
    endpwent();
2054

2055
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2056
2057
2058
2059
2060
2061
2062
}

/* 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
2063
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2064
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2065
2066
2067
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2068
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2069
2070
2071
2072

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

2073
    /* Okie, if there's a / in the buffer, strip out the directory part */
2074
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2075
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2076
2077
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2078
	    tmp--;
2079

Chris Allegretta's avatar
Chris Allegretta committed
2080
2081
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2082
2083
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2084

Chris Allegretta's avatar
Chris Allegretta committed
2085
    } else {
2086

Chris Allegretta's avatar
Chris Allegretta committed
2087
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2088
2089
2090
2091
2092
2093
	    return matches;
	else
	    tmp = buf;
    }

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

Chris Allegretta's avatar
Chris Allegretta committed
2099
2100
2101
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2102
2103

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2104
    fprintf(stderr, "\nDir = %s\n", dirname);
2105
2106
2107
2108
2109
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2110
    dir = opendir(dirname);
2111
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2112
2113
2114
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2115
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2116
2117
2118
2119
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2120
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2121
2122
#endif
	/* See if this matches */
2123
	if (check_wildcard_match(next->d_name, tmp)) {
2124
2125
2126
2127

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2128
2129
2130
2131
2132
2133
2134
2135

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

2136
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2137
2138
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2139
2140
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2141
		if (check_operating_dir(tmp2, TRUE) != 0) {
2142
2143
2144
2145
2146
2147
2148
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2149
	    tmp2 = NULL;
2150
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2151
2152
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2153
	    ++*num_matches;
2154
2155
2156
2157

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2158
2159
	}
    }
2160
2161
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2162

2163
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2164
2165
}

Chris Allegretta's avatar
Chris Allegretta committed
2166
2167
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
2168
2169
char *input_tab(char *buf, int place, bool *lastwastab, int *newplace,
	bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2170
2171
{
    /* Do TAB completion */
2172
    static int num_matches = 0, match_matches = 0;
2173
    static char **matches = (char **)NULL;
2174
    int pos = place, i = 0, col = 0, editline = 0;
2175
    int longestname = 0, is_dir = 0;
2176
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2177

2178
    *list = FALSE;
2179

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2180
    if (*lastwastab == FALSE) {
2181
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2182

2183
	*lastwastab = TRUE;
2184

Chris Allegretta's avatar
Chris Allegretta committed
2185
2186
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2187
2188
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2189

2190
2191
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2192
2193

	/* skip any leading white space */
2194
	while (*tmp && isblank(*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2195
2196
2197
2198
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2199
2200
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2201
	    free(matches);
2202
	    matches = (char **)NULL;
2203
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2204
2205
2206
2207
2208
	}

	/* 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
2209
2210
2211
2212
2213
	/* 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. */
2214
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2215
	    buf = mallocstrcpy(buf, tmp);
2216
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2217
	}
2218
2219
2220
2221
2222
2223
	/* 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
2224
2225
2226

	/* Try to match everything in the current working directory that
	 * matches.  */
2227
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2228
2229
2230
	    matches = cwd_tab_completion(tmp, &num_matches);

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

2233
2234
2235
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2236
	/* Did we find exactly one match? */
2237
	switch (num_matches) {
2238
	case 0:
2239
	    blank_edit();
2240
	    wrefresh(edit);
2241
2242
	    break;
	case 1:
2243

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

2246
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2247
2248
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2249
		tmp++;
2250
	    } else
2251
2252
		tmp = buf;

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

2256
	    if (is_dir != 0)
2257
		break;
2258
2259

	    copyto = tmp;
2260
2261
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2262
2263
		tmp++;

2264
	    /* write out the matched name */
2265
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2266
2267
	    *newplace += strlen(matches[0]) - pos;

2268
2269
2270
2271
2272
2273
2274
	    /* 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;

2275
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2276
	    append_slash_if_dir(buf, lastwastab, newplace);
2277

2278
2279
	    break;
	default:
2280
	    /* Check to see if all matches share a beginning, and, if so,
2281
	       tack it onto buf and then beep */
2282

2283
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2284
2285
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2286
		tmp++;
2287
	    } else
2288
2289
		tmp = buf;

2290
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2291
		 pos <= strlen(matches[0]); pos++)
2292
2293
		tmp++;

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

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

2327
	    editline = 0;
2328

2329
2330
2331
2332
2333
2334
2335
2336
	    /* 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;

2337
	    foo = charalloc(longestname + 5);
2338

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

2342
		/* make each filename shown be the same length as the longest
2343
		   filename, with two spaces at the end */
2344
2345
2346
2347
2348
2349
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2350
2351
		/* Disable el cursor */
		curs_set(0);
2352
2353
2354
2355
2356
2357
		/* 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 */
2358
		if (col > COLS - longestname && i + 1 < num_matches) {
2359
2360
		    editline++;
		    wmove(edit, editline, 0);
2361
2362
2363
2364
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2365
2366
2367
		    col = 0;
		}
	    }
2368
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2369
	    wrefresh(edit);
2370
	    *list = TRUE;
2371
2372
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2373
2374
    }

2375
2376
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
2377
    if (*list == FALSE)
2378
	edit_refresh();
2379
2380
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2381
}
2382
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2383

2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
/* 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;
}

2399
#ifndef DISABLE_BROWSER
2400
/* Our sort routine for file listings -- sort directories before
2401
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2402
2403
int diralphasort(const void *va, const void *vb)
{
2404
2405
2406
2407
    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
2408

2409
    if (aisdir != 0 && bisdir == 0)
2410
	return -1;
2411
    if (aisdir == 0 && bisdir != 0)
2412
	return 1;
2413

2414
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2415
2416
}

2417
/* Free our malloc()ed memory */
2418
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2419
{
2420
2421
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2422
2423
2424
    free(array);
}

2425
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2426
2427
void striponedir(char *foo)
{
2428
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2429

2430
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2431
    /* Don't strip the root dir */
2432
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2433
2434
	return;

2435
2436
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2437
    if (*tmp == '/')
2438
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2439
2440
2441
2442
2443

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

    if (tmp != foo)
2444
2445
2446
2447
2448
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2449
    }
Chris Allegretta's avatar
Chris Allegretta committed
2450
2451
}

2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
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;
}

2462
/* Initialize the browser code, including the list of files in *path */
2463
char **browser_init(const char *path, int *longest, int *numents)
2464
2465
2466
{
    DIR *dir;
    struct dirent *next;
2467
    char **filelist;
2468
    int i = 0;
2469
    size_t path_len;
2470
2471

    dir = opendir(path);
2472
    if (dir == NULL)
2473
2474
2475
2476
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
2477
	if (strcmp(next->d_name, ".") == 0)
2478
2479
2480
2481
2482
2483
2484
2485
	   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
2486
    filelist = (char **)nmalloc(*numents * sizeof (char *));
2487

2488
    if (strcmp(path, "/") == 0)
2489
2490
2491
	path = "";
    path_len = strlen(path);

2492
    while ((next = readdir(dir)) != NULL) {
2493
	if (strcmp(next->d_name, ".") == 0)
2494
2495
	   continue;

2496
2497
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2498
2499
	i++;
    }
2500
    closedir(dir);
2501
2502
2503
2504
2505
2506
2507

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2508
/* Our browser function.  inpath is the path to start browsing from */
2509
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2510
2511
2512
2513
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2514
2515
2516
    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;
2517
    bool meta_key, func_key;
2518
    char **filelist = (char **)NULL;
2519
#ifndef DISABLE_MOUSE
2520
2521
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2522

2523
2524
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2525
2526
2527
    /* 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 */
2528
    if (path != NULL && strcmp(path, inpath) != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2529
2530
2531
2532
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2533
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2534
    if (path == NULL)
2535
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2536
2537

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

2540
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2541
2542
2543
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2544
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2545
2546
2547
2548
2549
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2550
2551

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2552
    do {
2553
2554
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2555

2556
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2557

2558
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2559
	currshortcut = browser_list;
2560
2561
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2562
2563
 	editline = 0;
	col = 0;
2564
	    
2565
	/* Compute line number we're on now, so we don't divide by zero later */
2566
2567
2568
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2569
2570

	switch (kbinput) {
2571

2572
#ifndef DISABLE_MOUSE
2573
	case KEY_MOUSE:
2574
	    if (getmouse(&mevent) == ERR)
2575
		return retval;
2576
2577
2578
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2579
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2580
2581
2582
2583
		int selectedbackup = selected;

		mevent.y -= 2;

2584
2585
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2586
		selected = (lineno / editwinrows) * editwinrows * width
2587
2588
2589
2590
2591
2592
			+ 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--;
2593
2594
2595

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2596
		if (selected > numents - 1)
2597
		    selected = numents - 1;
2598
		else if (selectedbackup == selected)
2599
2600
2601
2602
		    /* Unget the 'select' key */
		    unget_kbinput('s', FALSE, FALSE);
	    } else {
		/* Must be clicking a shortcut */
2603
2604
2605
		int mouse_x, mouse_y;
		get_mouseinput(&mouse_x, &mouse_y, TRUE);
	    }
2606

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

2661
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2662
2663
2664
	    /* 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. */
2665
	    if (check_operating_dir(filelist[selected], FALSE) != 0) {
2666
2667
		statusbar(_("Can't go outside of %s in restricted mode"),
			operating_dir);
2668
2669
		beep();
		break;
2670
2671
2672
	    }
#endif

2673
	    if (stat(filelist[selected], &st) == -1) {
2674
2675
		statusbar(_("Can't open \"%s\": %s"), filelist[selected],
			strerror(errno));
2676
2677
2678
		beep();
		break;
	    }
2679

2680
2681
2682
2683
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2684
2685
	    }

2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
	    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
2696
		}
2697
2698
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2699

2700
2701
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
2702
2703
		statusbar(_("Can't open \"%s\": %s"), new_path,
			strerror(errno));
2704
2705
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2706
	    }
2707
2708
2709
2710
2711
2712
2713

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

2714
	/* Go to a specific directory */
2715
2716
	case NANO_GOTOLINE_KEY:
	case NANO_GOTOLINE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2717
2718
	case 'G': /* Pico compatibility */
	case 'g':
Rocco Corsi's avatar
   
Rocco Corsi committed
2719
	    curs_set(1);
2720
	    j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2721
#ifndef NANO_SMALL
2722
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2723
#endif
2724
		_("Go To Directory"));
2725
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2726
2727
2728
	    curs_set(0);

	    if (j < 0) {
2729
		statusbar(_("Cancelled"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2730
2731
2732
		break;
	    }

2733
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2734

2735
2736
2737
	    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
2738
2739
	    }

2740
#ifndef DISABLE_OPERATINGDIR
2741
	    if (check_operating_dir(new_path, FALSE) != 0) {
2742
2743
2744
2745
2746
2747
2748
		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
2749
2750
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2751
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2752
		break;
2753
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2754
2755

	    /* Start over again with the new path value */
2756
2757
	    free_charptrarray(filelist, numents);
	    free(foo);
2758
2759
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2760
2761
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2762
	/* Stuff we want to abort the browser */
2763
	case NANO_CANCEL_KEY:
2764
	case NANO_EXIT_KEY:
2765
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2766
2767
	case 'E': /* Pico compatibility */
	case 'e':
2768
2769
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2770
2771
2772
2773
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2774
2775
	blank_edit();

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2820
	    /* Highlight the currently selected file/dir */
2821
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2822
		wattron(edit, A_REVERSE);
2823
2824
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2825
2826
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2827
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2828
2829
2830
2831
2832
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2833
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2834
2835
2836
2837
2838
2839
2840
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
2841
	wrefresh(edit);
2842
2843
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
Chris Allegretta's avatar
Chris Allegretta committed
2844
2845
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2846
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2847
2848
    edit_refresh();

Chris Allegretta's avatar
Chris Allegretta committed
2849
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2850
2851
2852
2853
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2854

2855
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2856
 starts do_browser from there, else from the current dir */
2857
char *do_browse_from(const char *inpath)
2858
2859
{
    struct stat st;
2860
    char *bob;
2861
2862
2863
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2864

2865
2866
2867
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2868

2869
2870
2871
2872
2873
2874
2875
2876
    /*
     * 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);
2877
2878
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2879
	    path = getcwd(NULL, PATH_MAX + 1);
2880
	}
2881
    }
2882

2883
2884
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2885
    if (check_operating_dir(path, FALSE) != 0)
2886
2887
2888
2889
2890
2891
2892
2893
2894
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2895
    return bob;
2896
}
Chris Allegretta's avatar
Chris Allegretta committed
2897
#endif /* !DISABLE_BROWSER */
2898

2899
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2900
2901
2902
/* 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)
2903
{
2904
    char *nanohist = NULL;
2905

2906
2907
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2908

2909
2910
2911
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2912
    }
2913
2914
2915
2916
2917
2918
    return nanohist;
}

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

2920
    /* assume do_rcfile() has reported missing home dir */
2921
2922
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2923

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

/* save histories to ~/.nano_history */
void save_history(void)
{
2977
    char *nanohist;
2978
2979

    /* don't save unchanged or empty histories */
2980
    if ((search_history.count == 0 && replace_history.count == 0) ||
2981
	!ISSET(HISTORY_CHANGED) || ISSET(VIEW_MODE))
2982
2983
	return;

2984
2985
2986
2987
    nanohist = histfilename();

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

2989
	if (hist == NULL)
2990
	    rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2991
	else {
2992
2993
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2994
2995
2996
2997

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2998
		rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2999
3000
3001
3002
3003
	    fclose(hist);
	}
	free(nanohist);
    }
}
3004
#endif /* !NANO_SMALL && ENABLE_NANORC */