files.c 76.9 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-2003 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
28
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
Chris Allegretta's avatar
Chris Allegretta committed
29
#include <sys/wait.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
#include <sys/stat.h>
31
#include <utime.h>
Chris Allegretta's avatar
Chris Allegretta committed
32
33
#include <fcntl.h>
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
34
35
#include <ctype.h>
#include <dirent.h>
36
#include <pwd.h>
Chris Allegretta's avatar
Chris Allegretta committed
37
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
38
39
#include "proto.h"
#include "nano.h"
Chris Allegretta's avatar
Chris Allegretta committed
40

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

47
48
49
50
#ifndef NANO_SMALL
static int fileformat = 0;	/* 0 = *nix, 1 = DOS, 2 = Mac */
#endif

Chris Allegretta's avatar
Chris Allegretta committed
51
/* Load file into edit buffer - takes data from file struct. */
Chris Allegretta's avatar
Chris Allegretta committed
52
void load_file(int update)
Chris Allegretta's avatar
Chris Allegretta committed
53
54
{
    current = fileage;
55

56
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
57
    /* if update is zero, add a new entry to the open_files structure;
58
59
       otherwise, update the current entry (the latter is needed in the
       case of the alternate spell checker) */
Chris Allegretta's avatar
Chris Allegretta committed
60
    add_open_file(update);
61
62
#endif

63
64
#ifdef ENABLE_COLOR
    update_color();
Chris Allegretta's avatar
Chris Allegretta committed
65
    edit_refresh();
66
#endif
Chris Allegretta's avatar
Chris Allegretta committed
67
68
69
70
71
}

/* What happens when there is no file to open? aiee! */
void new_file(void)
{
72
    fileage = make_new_node(NULL);
73
    fileage->data = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
74
    fileage->data[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
75
76
77
78
    filebot = fileage;
    edittop = fileage;
    editbot = fileage;
    current = fileage;
79
    current_x = 0;
Chris Allegretta's avatar
Chris Allegretta committed
80
    totlines = 1;
81
    totsize = 0;
82
83
84

#ifdef ENABLE_MULTIBUFFER
    /* if there aren't any entries in open_files, create the entry for
85
86
       this new file; without this, if nano is started without a filename
       on the command line, a new file will be created, but it will be
Chris Allegretta's avatar
Chris Allegretta committed
87
       given no open_files entry */
88
    if (open_files == NULL) {
89
	add_open_file(0);
90
91
92
93
94
95
96
97
98
	/* turn off view mode in this case; this is for consistency
	   whether multibuffers are compiled in or not */
	UNSET(VIEW_MODE);
    }
#else
    /* if multibuffers haven't been compiled in, turn off view mode
       unconditionally; otherwise, don't turn them off (except in the
       above case), so that we can view multiple files properly */
    UNSET(VIEW_MODE);
99
100
#endif

101
102
#ifdef ENABLE_COLOR
    update_color();
Chris Allegretta's avatar
Chris Allegretta committed
103
    edit_refresh();
104
#endif
Chris Allegretta's avatar
Chris Allegretta committed
105
106
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
107
filestruct *read_line(char *buf, filestruct *prev, int *line1ins, int len)
Chris Allegretta's avatar
Chris Allegretta committed
108
{
109
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
110
111
112
113

    /* nulls to newlines; len is the string's real length here */
    unsunder(buf, len);

114
115
116
    assert(strlen(buf) == len);

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

118
#ifndef NANO_SMALL
119
120
    /* If it's a DOS file (CRLF), and file conversion isn't disabled,
       strip out the CR part */
121
122
    if (!ISSET(NO_CONVERT) && len > 0 && buf[len - 1] == '\r') {
	fileptr->data[len - 1] = '\0';
123
	totsize--;
124

125
	if (fileformat == 0)
126
	    fileformat = 1;
127
128
129
    }
#endif

130
    if (*line1ins != 0 || fileage == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
131
132
133
134
	/* Special case, insert with cursor on 1st line. */
	fileptr->prev = NULL;
	fileptr->next = fileage;
	fileptr->lineno = 1;
135
136
137
138
139
140
141
142
	if (*line1ins != 0) {
	    *line1ins = 0;
	    /* If we're inserting into the first line of the file, then
	       we want to make sure that our edit buffer stays on the
	       first line (and that fileage stays up to date!) */
	    edittop = fileptr;
	} else
	    filebot = fileptr;
Chris Allegretta's avatar
Chris Allegretta committed
143
	fileage = fileptr;
144
145
    } else {
	assert(prev != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
146
147
148
149
150
151
152
153
154
	fileptr->prev = prev;
	fileptr->next = NULL;
	fileptr->lineno = prev->lineno + 1;
	prev->next = fileptr;
    }

    return fileptr;
}

Chris Allegretta's avatar
Chris Allegretta committed
155
int read_file(FILE *f, const char *filename, int quiet)
Chris Allegretta's avatar
Chris Allegretta committed
156
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
157
    int num_lines = 0, len = 0;
Chris Allegretta's avatar
Chris Allegretta committed
158
    char input = '\0';		/* current input character */
Chris Allegretta's avatar
Chris Allegretta committed
159
160
161
    char *buf;
    long i = 0, bufx = 128;
    filestruct *fileptr = current, *tmp = NULL;
162
163
164
#ifndef NANO_SMALL
    int old_no_convert = ISSET(NO_CONVERT);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
165
    int line1ins = 0;
166
    int input_int;
Chris Allegretta's avatar
Chris Allegretta committed
167

168
    buf = charalloc(bufx);
169
    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
170

171
172
173
174
175
    if (current != NULL) {
	if (current == fileage)
	    line1ins = 1;
	else
	    fileptr = current->prev;
Chris Allegretta's avatar
Chris Allegretta committed
176
177
	tmp = fileptr;
    }
178
179

    /* For the assertion in read_line(), it must be true that if current is
Chris Allegretta's avatar
Chris Allegretta committed
180
     * NULL then so is fileage. */
181
182
    assert(current != NULL || fileage == NULL);

Chris Allegretta's avatar
Chris Allegretta committed
183
    /* Read the entire file into file struct. */
184
    while ((input_int = getc(f)) != EOF) {
Chris Allegretta's avatar
Chris Allegretta committed
185
        input = (char)input_int;
Chris Allegretta's avatar
Chris Allegretta committed
186
#ifndef NANO_SMALL
187
	/* If the file has binary chars in it, don't stupidly
188
189
190
191
192
	   assume it's a DOS or Mac formatted file if it hasn't been
	   detected as one already! */
	if (fileformat == 0 && !ISSET(NO_CONVERT)
		&& is_cntrl_char((int)input) != 0 && input != '\t'
		&& input != '\r' && input != '\n')
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
193
	    SET(NO_CONVERT);
Chris Allegretta's avatar
Chris Allegretta committed
194
195
#endif

196
	if (input == '\n') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
197
198
199
200
201
202
203

	    /* read in the line properly */
	    fileptr = read_line(buf, fileptr, &line1ins, len);

	    /* reset the line length, in preparation for the next line */
	    len = 0;

204
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
205
	    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
206
	    i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
207
#ifndef NANO_SMALL
208
209
	/* If it's a Mac file (no LF just a CR), and file conversion
	   isn't disabled, handle it! */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
210
	} else if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r') {
211
	    fileformat = 2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
212
213
214
215

	    /* read in the line properly */
	    fileptr = read_line(buf, fileptr, &line1ins, len);

216
217
218
219
	    /* reset the line length, in preparation for the next line;
	       since we've already read in the next character, reset it
	       to 1 instead of 0 */
	    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
230
231
232

	    /* Calculate the total length of the line; it might have
	       nulls in it, so we can't just use strlen(). */
	    len++;

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

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

    /* Did we not get a newline but still have stuff to do? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
255
    if (len > 0) {
256
257
258
259
#ifndef NANO_SMALL
	/* If file conversion isn't disabled, the last character in
	   this file is a CR and fileformat isn't set yet, make sure
	   it's set to Mac format */
260
	if (!ISSET(NO_CONVERT) && buf[len - 1] == '\r' && fileformat == 0)
261
262
263
	    fileformat = 2;
#endif

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
264
265
266
	/* read in the LAST line properly */
	fileptr = read_line(buf, fileptr, &line1ins, len);

267
	num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
268
269
	totsize++;
	buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
270
    }
271
#ifndef NANO_SMALL
272
    else if (!ISSET(NO_CONVERT) && input == '\r') {
273
274
275
	/* 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 */
276
277
278
279
280
281
282
	buf[0] = input;
	buf[1] = '\0';
	len = 1;
	fileptr = read_line(buf, fileptr, &line1ins, len);
	num_lines++;
	totsize++;
	buf[0] = '\0';
283
284
    }
#endif
285

286
287
288
289
290
291
292
293
294
    free(buf);

#ifndef NANO_SMALL
    /* If NO_CONVERT wasn't set before we read the file, but it is now,
       unset it again. */
    if (!old_no_convert && ISSET(NO_CONVERT))
	UNSET(NO_CONVERT);
#endif

295
    /* Did we even GET a file if we don't already have one? */
296
    if (totsize == 0 || fileptr == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
297
	new_file();
Robert Siemborski's avatar
Robert Siemborski committed
298

299
    /* Did we try to insert a file of 0 bytes? */
300
301
302
303
304
305
306
307
308
309
310
311
312
    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--;
	    load_file(quiet);
	}
Chris Allegretta's avatar
Chris Allegretta committed
313
    }
314
315
316

#ifndef NANO_SMALL
    if (fileformat == 2)
317
	statusbar(P_("Read %d line (Converted from Mac format)",
318
319
			"Read %d lines (Converted from Mac format)",
			num_lines), num_lines);
320
    else if (fileformat == 1)
321
	statusbar(P_("Read %d line (Converted from DOS format)",
322
323
			"Read %d lines (Converted from DOS format)",
			num_lines), num_lines);
324
325
    else
#endif
326
	statusbar(P_("Read %d line", "Read %d lines", num_lines),
327
			num_lines);
328

329
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
330
331
332
333

    return 1;
}

Chris Allegretta's avatar
Chris Allegretta committed
334
/* Open the file (and decide if it exists). */
Chris Allegretta's avatar
Chris Allegretta committed
335
int open_file(const char *filename, int insert, int quiet)
Chris Allegretta's avatar
Chris Allegretta committed
336
337
{
    int fd;
338
    FILE *f;
Chris Allegretta's avatar
Chris Allegretta committed
339
340
    struct stat fileinfo;

Chris Allegretta's avatar
Chris Allegretta committed
341
    if (filename[0] == '\0' || stat(filename, &fileinfo) == -1) {
342
343
	if (insert && !quiet) {
	    statusbar(_("\"%s\" not found"), filename);
Chris Allegretta's avatar
Chris Allegretta committed
344
345
346
347
348
349
	    return -1;
	} else {
	    /* We have a new file */
	    statusbar(_("New File"));
	    new_file();
	}
350
351
352
353
354
355
356
357
    } 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);
	if (!insert)
	    new_file();
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
358
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
Chris Allegretta's avatar
Chris Allegretta committed
359
360
361
362
363
364
365
	/* If we're in multibuffer mode, don't be quiet when an error
	   occurs while opening a file */
	if (!quiet
#ifdef ENABLE_MULTIBUFFER
		|| ISSET(MULTIBUFFER)
#endif
		)
Chris Allegretta's avatar
Chris Allegretta committed
366
	    statusbar("%s: %s", strerror(errno), filename);
367
368
	if (!insert)
	    new_file();
Chris Allegretta's avatar
Chris Allegretta committed
369
370
371
372
	return -1;
    } else {			/* File is A-OK */
	if (!quiet)
	    statusbar(_("Reading File"));
373
	f = fdopen(fd, "rb"); /* Binary for our own line-end munging */
374
	if (f == NULL) {
375
	    nperror("fdopen");
376
	    close(fd);
377
378
379
	    return -1;
	}
	read_file(f, filename, quiet);
380
381
382
#ifndef NANO_SMALL
	stat(filename, &originalfilestat);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
383
384
385
386
387
    }

    return 1;
}

388
/* This function will return the name of the first available extension
Chris Allegretta's avatar
Chris Allegretta committed
389
390
391
 * of a filename (starting with the filename, then filename.1, etc).
 * Memory is allocated for the return value.  If no writable extension
 * exists, we return "". */
Chris Allegretta's avatar
Chris Allegretta committed
392
char *get_next_filename(const char *name)
393
394
395
396
397
398
399
400
{
    int i = 0;
    char *buf = NULL;
    struct stat fs;

    buf = charalloc(strlen(name) + num_of_digits(INT_MAX) + 2);
    strcpy(buf, name);

Chris Allegretta's avatar
Chris Allegretta committed
401
    while (1) {
402
403

	if (stat(buf, &fs) == -1)
Chris Allegretta's avatar
Chris Allegretta committed
404
	    return buf;
405
406
407
408
409
410
411
412
	if (i == INT_MAX)
	    break;

	i++;
	strcpy(buf, name);
	sprintf(&buf[strlen(name)], ".%d", i);
    }

Chris Allegretta's avatar
Chris Allegretta committed
413
414
    /* We get here only if there is no possible save file. */
    buf[0] = '\0';
415
416
417
418

    return buf;
}

419
int do_insertfile(int loading_file)
Chris Allegretta's avatar
Chris Allegretta committed
420
{
Chris Allegretta's avatar
Chris Allegretta committed
421
    int i, old_current_x = current_x;
422
    char *realname = NULL;
423
424
    static char *inspath = NULL;

425
426
427
428
    if (inspath == NULL) {
	inspath = charalloc(1);
	inspath[0] = '\0';
    }
Chris Allegretta's avatar
Chris Allegretta committed
429
430

    wrap_reset();
431

432
433
434
435
#if !defined(DISABLE_BROWSER) || !defined(NANO_SMALL) && defined(ENABLE_MULTIBUFFER)
  start_again:	/* Goto here when the user cancels the file browser. */
#endif

436
#if !defined(DISABLE_BROWSER) || (!defined(DISABLE_MOUSE) && defined(NCURSES_MOUSE_VERSION))
437
    currshortcut = insertfile_list;
438
439
#endif

440
#ifndef DISABLE_OPERATINGDIR
441
    if (operating_dir != NULL && strcmp(operating_dir, ".") != 0)
442
443
#ifdef ENABLE_MULTIBUFFER 
	if (ISSET(MULTIBUFFER))
Chris Allegretta's avatar
Chris Allegretta committed
444
445
446
447
448
	    i = statusq(1, insertfile_list, inspath,
#ifndef NANO_SMALL
		0,
#endif
		_("File to insert into new buffer [from %s] "),
449
450
451
		operating_dir);
	else
#endif
Chris Allegretta's avatar
Chris Allegretta committed
452
453
454
455
456
	    i = statusq(1, insertfile_list, inspath,
#ifndef NANO_SMALL
		0,
#endif
		_("File to insert [from %s] "),
457
		operating_dir);
458

459
    else
460
#endif
461
462
#ifdef ENABLE_MULTIBUFFER 
	if (ISSET(MULTIBUFFER))
Chris Allegretta's avatar
Chris Allegretta committed
463
464
465
466
467
	    i = statusq(1, insertfile_list, inspath,
#ifndef NANO_SMALL
		0,
#endif
		_("File to insert into new buffer [from ./] "));
468
	else
Chris Allegretta's avatar
Chris Allegretta committed
469
470
471
472
#endif /* ENABLE_MULTIBUFFER */
	    i = statusq(1, insertfile_list, inspath,
#ifndef NANO_SMALL
		0,
473
#endif
Chris Allegretta's avatar
Chris Allegretta committed
474
		_("File to insert [from ./] "));
475

Chris Allegretta's avatar
Chris Allegretta committed
476
    if (i != -1) {
477
	inspath = mallocstrcpy(inspath, answer);
Chris Allegretta's avatar
Chris Allegretta committed
478
#ifdef DEBUG
479
	fprintf(stderr, _("filename is %s\n"), answer);
Chris Allegretta's avatar
Chris Allegretta committed
480
481
#endif

482
#ifndef NANO_SMALL
483
#ifdef ENABLE_MULTIBUFFER
484
	if (i == TOGGLE_LOAD_KEY) {
485
486
	    /* Don't allow toggling if we're in view mode. */
	    if (!ISSET(VIEW_MODE))
487
		TOGGLE(MULTIBUFFER);
488
489
	    loading_file = ISSET(MULTIBUFFER);
	    goto start_again;
490
	}
491
#endif /* ENABLE_MULTIBUFFER */
492

493
	if (i == NANO_EXTCMD_KEY) {
494
495
496
	    int ts = statusq(TRUE, extcmd_list, "", NULL, 
		_("Command to execute"));
	    if (ts  == -1 || answer == NULL || answer[0] == '\0') {
497
		statusbar(_("Cancelled"));
498
499
500
501
502
		UNSET(KEEP_CUTBUFFER);
		display_main_list();
		return 0;
	    }
	}
503
#endif /* !NANO_SMALL */
504
505
506
507
#ifndef DISABLE_BROWSER
	if (i == NANO_TOFILES_KEY) {
	    char *tmp = do_browse_from(answer);

508
509
	    if (tmp != NULL) {
		free(answer);
510
		answer = tmp;
511
		resetstatuspos = 1;
512
	    } else
513
514
515
516
517
518
519
520
521
522
523
		goto start_again;
	}
#endif

#ifndef DISABLE_OPERATINGDIR
	if (i != NANO_EXTCMD_KEY && check_operating_dir(answer, FALSE)) {
	    statusbar(_("Can't insert file from outside of %s"),
			operating_dir);
	    return 0;
	}
#endif
524

525
#ifdef ENABLE_MULTIBUFFER
526
	if (loading_file) {
527
528
	    /* update the current entry in the open_files structure */
	    add_open_file(1);
529
530
	    new_file();
	    UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
531
532
533
#ifndef NANO_SMALL
	    UNSET(MARK_ISSET);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
534
535
536
	}
#endif

537
#ifndef NANO_SMALL
538
539
	if (i == NANO_EXTCMD_KEY) {
	    realname = mallocstrcpy(realname, "");
540
	    i = open_pipe(answer);
541
	} else
542
#endif /* NANO_SMALL */
543
544
	{
	    realname = real_dir_from_tilde(answer);
545
	    i = open_file(realname, 1, loading_file);
546
	}
547

548
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
549
550
551
552
553
554
555
556
557
558
559
560
561
562
	if (loading_file) {
	    /* if there was an error opening the file, free() realname,
	       free() fileage (which now points to the new buffer we
	       created to hold the file), reload the buffer we had open
	       before, and skip the insertion; otherwise, save realname
	       in filename and continue the insertion */
	    if (i == -1) {
		free(realname);
		free(fileage);
		load_open_file();
		goto skip_insert;
	    } else
		filename = mallocstrcpy(filename, realname);
	}
563
564
#endif

565
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
566

Chris Allegretta's avatar
Chris Allegretta committed
567
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
568
	dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
569
#endif
570

571
#ifdef ENABLE_MULTIBUFFER
572
	if (loading_file)
573
	    load_file(0);
574
575
576
	else
#endif
	    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
577
578

	/* Here we want to rebuild the edit window */
Robert Siemborski's avatar
Robert Siemborski committed
579
	fix_editbot();
Chris Allegretta's avatar
Chris Allegretta committed
580

581
#ifdef ENABLE_MULTIBUFFER
582
583
584
585
586
587
588
589
590
591
	/* If we've loaded another file, update the titlebar's contents */
	if (loading_file) {
	    clearok(topwin, FALSE);
	    titlebar(NULL);

	    /* And re-init the shortcut list */
	    shortcut_init(0);
	}
#endif

Chris Allegretta's avatar
Chris Allegretta committed
592
593
594
595
596
597
598
599
600
601
602
#ifdef ENABLE_MULTIBUFFER
	if (!loading_file) {
#endif

	    /* Restore the old x-coordinate position */
	    current_x = old_current_x;

#ifdef ENABLE_MULTIBUFFER
	}
#endif

603
	/* If we've gone off the bottom, recenter; otherwise, just redraw */
Chris Allegretta's avatar
Chris Allegretta committed
604
	if (current->lineno > editbot->lineno)
605
	    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
606
607
608
609
610
	else
	    edit_refresh();

    } else {
	statusbar(_("Cancelled"));
611
	i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
612
    }
613

Chris Allegretta's avatar
Chris Allegretta committed
614
615
616
617
#ifdef ENABLE_MULTIBUFFER
  skip_insert:
#endif

618
619
620
621
622
623
    free(inspath);
    inspath = NULL;

    UNSET(KEEP_CUTBUFFER);
    display_main_list();
    return i;
Chris Allegretta's avatar
Chris Allegretta committed
624
625
}

626
627
628
int do_insertfile_void(void)
{
    int result = 0;
629
#ifdef ENABLE_MULTIBUFFER
630
631
632
633
634
635
636
637
    if (ISSET(VIEW_MODE)) {
	if (ISSET(MULTIBUFFER))
	    result = do_insertfile(1);
	else
	    statusbar(_("Key illegal in non-multibuffer mode"));
    }
    else
	result = do_insertfile(ISSET(MULTIBUFFER));
638
639
640
641
642
643
644
645
#else
    result = do_insertfile(0);
#endif

    display_main_list();
    return result;
}

646
#ifdef ENABLE_MULTIBUFFER
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
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
/* Create a new openfilestruct node. */
openfilestruct *make_new_opennode(openfilestruct *prevnode)
{
    openfilestruct *newnode = nmalloc(sizeof(openfilestruct));

    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,
		     openfilestruct *end)
{
    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
706
	    fprintf(stderr, _("%s: free'd a node, YAY!\n"), "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
707
708
709
710
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
711
	fprintf(stderr, _("%s: free'd last node.\n"), "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
712
713
714
715
#endif
    }
}

716
/*
717
 * Add/update an entry to the open_files openfilestruct.  If update is
718
 * zero, a new entry is created; otherwise, the current entry is updated.
719
 * Return 0 on success or 1 on error.
720
 */
721
int add_open_file(int update)
722
{
723
    openfilestruct *tmp;
724

725
    if (fileage == NULL || current == NULL || filename == NULL)
726
727
728
	return 1;

    /* if no entries, make the first one */
729
    if (open_files == NULL)
730
	open_files = make_new_opennode(NULL);
731
732
733
734
735
736
737

    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
738
	fprintf(stderr, _("filename is %s\n"), open_files->filename);
739
740
#endif

741
742
	tmp = make_new_opennode(NULL);
	splice_opennode(open_files, tmp, open_files->next);
743
744
745
746
	open_files = open_files->next;
    }

    /* save current filename */
747
    open_files->filename = mallocstrcpy(open_files->filename, filename);
748

749
750
751
752
753
#ifndef NANO_SMALL
    /* save the file's stat */
    open_files->originalfilestat = originalfilestat;
#endif

754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
    /* 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 */
770
    open_files->file_lineno = current->lineno;
771

Chris Allegretta's avatar
Chris Allegretta committed
772
773
774
775
776
777
778
779
780
781
782
783
784
785
    /* if we're updating, save current modification status (and marking
       status, if available) */
    if (update) {
#ifndef NANO_SMALL
	open_files->file_flags = (MODIFIED & ISSET(MODIFIED)) | (MARK_ISSET & ISSET(MARK_ISSET));
	if (ISSET(MARK_ISSET)) {
	    open_files->file_mark_beginbuf = mark_beginbuf;
	    open_files->file_mark_beginx = mark_beginx;
	}
#else
	open_files->file_flags = (MODIFIED & ISSET(MODIFIED));
#endif
    }

786
787
788
    /* 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
789
790
791
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
792
    }
793
794

#ifdef DEBUG
795
    fprintf(stderr, _("filename is %s\n"), open_files->filename);
796
797
798
799
800
801
802
803
804
805
806
807
#endif

    return 0;
}

/*
 * Read the current entry in the open_files structure and set up the
 * currently open file using that entry's information.  Return 0 on
 * success or 1 on error.
 */
int load_open_file(void)
{
808
    if (open_files == NULL)
809
810
811
812
	return 1;

    /* set up the filename, the file buffer, the total number of lines in
       the file, and the total file size */
813
    filename = mallocstrcpy(filename, open_files->filename);
814
815
816
#ifndef NANO_SMALL
    originalfilestat = open_files->originalfilestat;
#endif
817
    fileage = open_files->fileage;
818
    current = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
819
    filebot = open_files->filebot;
820
821
822
    totlines = open_files->file_totlines;
    totsize = open_files->file_totsize;

Chris Allegretta's avatar
Chris Allegretta committed
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
    /* restore modification status */
    if (open_files->file_flags & MODIFIED)
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

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

Chris Allegretta's avatar
Chris Allegretta committed
839
840
841
842
#ifdef ENABLE_COLOR
    update_color();
#endif

843
844
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
845
    do_gotopos(open_files->file_lineno, open_files->file_current_x, open_files->file_current_y, open_files->file_placewewant);
846

Chris Allegretta's avatar
Chris Allegretta committed
847
    /* update the titlebar */
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
    clearok(topwin, FALSE);
    titlebar(NULL);

    /* now we're done */
    return 0;
}

/*
 * Open the previous entry in the open_files structure.  If closing_file
 * is zero, update the current entry before switching from it.
 * Otherwise, we are about to close that entry, so don't bother doing so.
 * Return 0 on success and 1 on error.
 */
int open_prevfile(int closing_file)
{
863
    if (open_files == NULL)
864
865
866
	return 1;

    /* if we're not about to close the current entry, update it before
867
       doing anything */
868
    if (!closing_file)
869
	add_open_file(1);
870

871
    if (open_files->prev == NULL && open_files->next == NULL) {
872
873
874
875
876
877
878

	/* only one file open */
	if (!closing_file)
	    statusbar(_("No more open files"));
	return 1;
    }

879
    if (open_files->prev != NULL) {
880
881
882
	open_files = open_files->prev;

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

    }

888
    else if (open_files->next != NULL) {
889
890

	/* if we're at the beginning, wrap around to the end */
891
	while (open_files->next != NULL)
892
893
894
	    open_files = open_files->next;

#ifdef DEBUG
895
	    fprintf(stderr, _("filename is %s\n"), open_files->filename);
896
897
898
899
900
901
#endif

    }

    load_open_file();

902
    statusbar(_("Switched to %s"),
903
      ((open_files->filename[0] == '\0') ? "New Buffer" : open_files->filename));
904

905
906
907
908
909
910
911
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

912
913
914
/* This function is used by the shortcut list. */
int open_prevfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
915
    return open_prevfile(0);
916
917
}

918
919
920
921
922
923
924
925
/*
 * Open the next entry in the open_files structure.  If closing_file is
 * zero, update the current entry before switching from it.  Otherwise, we
 * are about to close that entry, so don't bother doing so.  Return 0 on
 * success and 1 on error.
 */
int open_nextfile(int closing_file)
{
926
    if (open_files == NULL)
927
928
929
	return 1;

    /* if we're not about to close the current entry, update it before
930
       doing anything */
931
    if (!closing_file)
932
	add_open_file(1);
933

934
    if (open_files->prev == NULL && open_files->next == NULL) {
935
936
937
938
939
940
941

	/* only one file open */
	if (!closing_file)
	    statusbar(_("No more open files"));
	return 1;
    }

942
    if (open_files->next != NULL) {
943
944
945
	open_files = open_files->next;

#ifdef DEBUG
946
	fprintf(stderr, _("filename is %s\n"), open_files->filename);
947
948
949
#endif

    }
950
    else if (open_files->prev != NULL) {
951
952

	/* if we're at the end, wrap around to the beginning */
953
	while (open_files->prev != NULL) {
954
955
956
	    open_files = open_files->prev;

#ifdef DEBUG
957
	    fprintf(stderr, _("filename is %s\n"), open_files->filename);
958
959
960
961
962
963
964
#endif

	}
    }

    load_open_file();

965
    statusbar(_("Switched to %s"),
966
      ((open_files->filename[0] == '\0') ? "New Buffer" : open_files->filename));
967

968
969
970
971
972
973
974
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

975
976
977
/* This function is used by the shortcut list. */
int open_nextfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
978
    return open_nextfile(0);
979
980
}

981
982
/*
 * Delete an entry from the open_files filestruct.  After deletion of an
983
 * entry, the next or previous entry is opened, whichever is found first.
984
985
986
987
 * Return 0 on success or 1 on error.
 */
int close_open_file(void)
{
988
    openfilestruct *tmp;
989

990
    if (open_files == NULL)
991
992
	return 1;

Chris Allegretta's avatar
Chris Allegretta committed
993
994
995
    /* 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 */
996
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
997
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
998

999
    tmp = open_files;
1000
1001
    if (open_nextfile(1)) {
	if (open_prevfile(1))
1002
1003
1004
	    return 1;
    }

1005
1006
    unlink_opennode(tmp);
    delete_opennode(tmp);
1007
1008
1009
1010
1011

    shortcut_init(0);
    display_main_list();
    return 0;
}
1012
#endif /* MULTIBUFFER */
1013

1014
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
1015
/*
1016
1017
1018
1019
1020
1021
 * 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).
1022
 */
1023
char *get_full_path(const char *origpath)
1024
{
1025
1026
1027
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
1028
    char *expanded_origpath;
1029

1030
    /* first, get the current directory, and tack a slash onto the end of
1031
       it, unless it turns out to be "/", in which case leave it alone */
1032
1033
1034
1035
1036
1037
1038

#ifdef PATH_MAX
    d_here = getcwd(NULL, PATH_MAX + 1);
#else
    d_here = getcwd(NULL, 0);
#endif

1039
    if (d_here != NULL) {
1040
1041

	align(&d_here);
1042
1043
1044
1045
	if (strcmp(d_here, "/")) {
	    d_here = nrealloc(d_here, strlen(d_here) + 2);
	    strcat(d_here, "/");
	}
1046
1047
1048
1049
1050

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

1053
	expanded_origpath = real_dir_from_tilde(origpath);
1054
	/* save the value of origpath in both d_there and d_there_file */
1055
1056
1057
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1058

1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
	/* 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 != '/') {
		d_there = nrealloc(d_there, strlen(d_there) + 2);
		strcat(d_there, "/");
		d_there_file = nrealloc(d_there_file, strlen(d_there_file) + 2);
		strcat(d_there_file, "/");
	    }
	}

1072
1073
1074
1075
1076
	/* 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 */
1077
	if (last_slash == NULL)
1078
	    d_there = mallocstrcpy(d_there, d_here);
1079
	else {
1080
1081
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1082
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1083
	    null_at(&d_there, last_slash_index + 1);
1084
1085
1086

	    /* and remove all non-file elements from d_there_file (i. e.
	       everything before and including the last slash); if we
1087
	       have a path but no filename, don't do anything */
1088
1089
1090
1091
1092
1093
	    if (!path_only) {
		last_slash = strrchr(d_there_file, '/');
		last_slash++;
		strcpy(d_there_file, last_slash);
		align(&d_there_file);
	    }
1094
1095
1096

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1097
1098
1099
		/* 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 */
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109

		free(d_there);

#ifdef PATH_MAX
		d_there = getcwd(NULL, PATH_MAX + 1);
#else
		d_there = getcwd(NULL, 0);
#endif

		align(&d_there);
1110
		if (d_there != NULL) {
1111
1112
1113
1114
1115
1116
1117

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
		    if (strcmp(d_there, "/")) {
			d_there = nrealloc(d_there, strlen(d_there) + 2);
			strcat(d_there, "/");
		    }
1118
1119
1120
		}
		else
		    return NULL;
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
	    }

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

1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
	/* 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);
	}
1142
1143
1144
1145
1146
1147
1148
1149
1150

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

    return newpath;
}
1151
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1152
1153
1154

#ifndef DISABLE_SPELLER
/*
1155
1156
1157
 * 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.
1158
 */
1159
char *check_writable_directory(const char *path)
1160
{
1161
    char *full_path = get_full_path(path);
1162
    int writable;
1163
1164
    struct stat fileinfo;

1165
    /* if get_full_path() failed, return NULL */
1166
    if (full_path == NULL)
1167
	return NULL;
1168
1169
1170

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

    /* if the full path doesn't end in a slash (meaning get_full_path()
1174
1175
1176
1177
       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);
1178
	return NULL;
1179
    }
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189

    /* 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
1190
1191
1192
1193
1194
1195
1196
 * 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.
1197
 */
1198
1199
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1200
1201
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1202
    int filedesc;
1203

1204
1205
      /* 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,
1206
         leave full_tempdir set to NULL */
1207
    TMPDIR_env = getenv("TMPDIR");
1208
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1209
	full_tempdir = check_writable_directory(TMPDIR_env);
1210

1211
1212
1213
    /* 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 */
1214
    if (full_tempdir == NULL && dirname != NULL)
1215
	full_tempdir = check_writable_directory(dirname);
1216
1217
1218

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1219
    if (full_tempdir == NULL)
1220
	full_tempdir = check_writable_directory(P_tmpdir);
1221
1222

    /* if P_tmpdir didn't work, use /tmp instead */
1223
    if (full_tempdir == NULL) {
1224
1225
1226
1227
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

1228
    full_tempdir = nrealloc(full_tempdir, strlen(full_tempdir) + 12);
1229
1230

    /* like tempnam(), use only the first 5 characters of the prefix */
1231
1232
1233
1234
1235
1236
1237
1238
    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) {
1239
	close(filedesc);
1240
1241
	unlink(full_tempdir);
	return full_tempdir;
1242
    }
1243
1244
1245

    free(full_tempdir);
    return NULL;
1246
1247
}
#endif /* !DISABLE_SPELLER */
1248
1249

#ifndef DISABLE_OPERATINGDIR
1250
1251
1252
1253
1254
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1255
    if (operating_dir == NULL)
1256
1257
1258
1259
1260
	return;
    full_operating_dir = get_full_path(operating_dir);

    /* If get_full_path() failed or the operating directory is
       inaccessible, unset operating_dir. */
1261
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1262
1263
1264
1265
1266
1267
1268
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1269
1270
1271
1272
1273
1274
/*
 * Check to see if we're inside the operating directory.  Return 0 if we
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
 * completion will work.
 */
1275
int check_operating_dir(const char *currpath, int allow_tabcomp)
1276
{
1277
1278
1279
    /* The char *full_operating_dir is global for mem cleanup, and
       therefore we only need to get it the first time this function
       is called; also, a relative operating directory path will
1280
1281
       only be handled properly if this is done */

1282
1283
1284
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1285
1286

    /* if no operating directory is set, don't bother doing anything */
1287
    if (operating_dir == NULL)
1288
1289
1290
	return 0;

    fullpath = get_full_path(currpath);
1291
    if (fullpath == NULL)
1292
1293
1294
1295
1296
1297
1298
	return 1;

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

    /* if both searches failed, we're outside the operating directory */
1299
    /* otherwise */
1300
1301
1302
1303
1304
    /* 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 */
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1305
1306
	retval = 1;
    free(fullpath);	
1307
    /* otherwise, we're still inside it */
1308
    return retval;
1309
}
1310
1311
#endif

Chris Allegretta's avatar
Chris Allegretta committed
1312
1313
/*
 * Write a file out.  If tmp is nonzero, we set the umask to 0600,
1314
 * we don't set the global variable filename to its name, and don't
Chris Allegretta's avatar
Chris Allegretta committed
1315
 * print out how many lines we wrote on the statusbar.
Chris Allegretta's avatar
Chris Allegretta committed
1316
 *
1317
 * tmp means we are writing a tmp file in a secure fashion.  We use
1318
 * it when spell checking or dumping the file on an error.
1319
 *
1320
1321
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1322
1323
 *
 * nonamechange means don't change the current filename, it is ignored
1324
 * if tmp is nonzero or if we're appending/prepending.
Chris Allegretta's avatar
Chris Allegretta committed
1325
 */
1326
int write_file(const char *name, int tmp, int append, int nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1327
{
1328
1329
1330
    int retval = -1;
	/* Instead of returning in this function, you should always
	 * merely set retval then goto cleanup_and_exit. */
1331
1332
    long size;
    int lineswritten = 0;
1333
    char *buf = NULL;
1334
    const filestruct *fileptr;
1335
1336
1337
    FILE *f;
    int fd;
    int mask = 0, realexists, anyexists;
1338
    struct stat st, lst;
1339
    char *realname = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
1340

Chris Allegretta's avatar
Chris Allegretta committed
1341
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1342
1343
1344
	statusbar(_("Cancelled"));
	return -1;
    }
1345
1346
    if (!tmp)
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1347
    fileptr = fileage;
1348

1349
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1350

1351
#ifndef DISABLE_OPERATINGDIR
1352
1353
    /* If we're writing a temporary file, we're probably going outside
       the operating directory, so skip the operating directory test. */
1354
    if (!tmp && operating_dir != NULL && check_operating_dir(realname, 0)) {
1355
1356
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1357
1358
1359
    }
#endif

1360
1361
    /* Save the state of file at the end of the symlink (if there is
       one). */
1362
1363
    realexists = stat(realname, &st);

1364
1365
1366
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
       temporary, and the file already exists.  Furthermore, if we aren't
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1367
       appending, prepending, or writing a selection, we backup only if
1368
1369
1370
       the file has not been modified by someone else since nano opened
       it. */
    if (ISSET(BACKUP_FILE) && !tmp && realexists == 0 &&
1371
	    (append != 0 || ISSET(MARK_ISSET) ||
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
		originalfilestat.st_mtime == st.st_mtime)) {
	FILE *backup_file;
	char *backupname = NULL;
	char backupbuf[COPYFILEBLOCKSIZE];
	size_t bytesread;
	struct utimbuf filetime;

	/* save the original file's access and modification times */
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

	/* open the original file to copy to the backup */
	f = fopen(realname, "rb");
1385
	if (f == NULL) {
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
	    statusbar(_("Could not read %s for backup: %s"), realname,
		strerror(errno));
	    return -1;
	}

	backupname = charalloc(strlen(realname) + 2);
	sprintf(backupname, "%s~", realname);

	/* get a file descriptor for the destination backup file */
	backup_file = fopen(backupname, "wb");
1396
	if (backup_file == NULL) {
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
	    statusbar(_("Couldn't write backup: %s"), strerror(errno));
	    free(backupname);
	    return -1;
	}

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

	/* copy the file */
	while ((bytesread = fread(backupbuf, sizeof(char),
		COPYFILEBLOCKSIZE, f)) > 0)
	    if (fwrite(backupbuf, sizeof(char), bytesread, backup_file) <= 0)
		break;
	fclose(backup_file);
	fclose(f);

	if (chmod(backupname, originalfilestat.st_mode) == -1)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1415
	    statusbar(_("Could not set permissions %o on backup %s: %s"),
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
			originalfilestat.st_mode, backupname,
			strerror(errno));

	if (chown(backupname, originalfilestat.st_uid,
		originalfilestat.st_gid) == -1)
	    statusbar(_("Could not set owner %d/group %d on backup %s: %s"),
			originalfilestat.st_uid, originalfilestat.st_gid,
			backupname, strerror(errno));

	if (utime(backupname, &filetime) == -1)
	    statusbar(_("Could not set access/modification time on backup %s: %s"),
			backupname, strerror(errno));

	free(backupname);
    }
#endif

1433
1434
    /* Stat the link itself for the check... */
    anyexists = lstat(realname, &lst);
1435

1436
1437
    /* New case: if the file exists, just give up */
    if (tmp && anyexists != -1)
1438
	goto cleanup_and_exit;
1439
    /* NOTE: If you change this statement, you MUST CHANGE the if 
1440
       statement below (that says:
1441
		if (realexists == -1 || tmp || (ISSET(NOFOLLOW_SYMLINKS) &&
1442
		S_ISLNK(lst.st_mode))) {
1443
       to reflect whether or not to link/unlink/rename the file */
1444
    else if (append != 2 && (!ISSET(NOFOLLOW_SYMLINKS) || !S_ISLNK(lst.st_mode) 
1445
		|| tmp)) {
1446
1447
1448
	/* Use O_EXCL if tmp is nonzero.  This is now copied from joe,
	   because wiggy says so *shrug*. */
	if (append != 0)
Chris Allegretta's avatar
Chris Allegretta committed
1449
	    fd = open(realname, O_WRONLY | O_CREAT | O_APPEND, (S_IRUSR | S_IWUSR));
1450
	else if (tmp)
Chris Allegretta's avatar
Chris Allegretta committed
1451
	    fd = open(realname, O_WRONLY | O_CREAT | O_EXCL, (S_IRUSR | S_IWUSR));
1452
	else
Chris Allegretta's avatar
Chris Allegretta committed
1453
	    fd = open(realname, O_WRONLY | O_CREAT | O_TRUNC, (S_IRUSR | S_IWUSR));
1454
1455

	/* First, just give up if we couldn't even open the file */
1456
	if (fd == -1) {
1457
	    if (!tmp && ISSET(TEMP_OPT)) {
Chris Allegretta's avatar
Chris Allegretta committed
1458
		UNSET(TEMP_OPT);
1459
1460
1461
1462
1463
		retval = do_writeout(filename, 1, 0);
	    } else
		statusbar(_("Could not open file for writing: %s"),
			strerror(errno));
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1464
	}
1465

Chris Allegretta's avatar
Chris Allegretta committed
1466
1467
1468
    }
    /* Don't follow symlink.  Create new file. */
    else {
1469
	buf = charalloc(strlen(realname) + 8);
1470
	strcpy(buf, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1471
1472
	strcat(buf, ".XXXXXX");
	if ((fd = mkstemp(buf)) == -1) {
Chris Allegretta's avatar
Chris Allegretta committed
1473
1474
	    if (ISSET(TEMP_OPT)) {
		UNSET(TEMP_OPT);
1475
1476
1477
1478
1479
		retval = do_writeout(filename, 1, 0);
	    } else
		statusbar(_("Could not open file for writing: %s"),
			strerror(errno));
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1480
1481
1482
	}
    }

Chris Allegretta's avatar
Chris Allegretta committed
1483
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
1484
    dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
1485
#endif
1486

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1487
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1488
    if (f == NULL) {
1489
1490
	statusbar(_("Could not open file for writing: %s"), strerror(errno));
	goto cleanup_and_exit;
1491
1492
    }

Chris Allegretta's avatar
Chris Allegretta committed
1493
    while (fileptr != NULL && fileptr->next != NULL) {
1494
	int data_len;
1495

Robert Siemborski's avatar
Robert Siemborski committed
1496
	/* Next line is so we discount the "magic line" */
1497
1498
	if (filebot == fileptr && fileptr->data[0] == '\0')
	    break;
Robert Siemborski's avatar
Robert Siemborski committed
1499

1500
	data_len = strlen(fileptr->data);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1501
1502
1503
1504

	/* newlines to nulls, just before we write to disk */
	sunder(fileptr->data);

1505
	size = fwrite(fileptr->data, 1, data_len, f);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1506
1507
1508
1509

	/* nulls to newlines; data_len is the string's real length here */
	unsunder(fileptr->data, data_len);

1510
	if (size < data_len) {
Chris Allegretta's avatar
Chris Allegretta committed
1511
1512
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
1513
	    fclose(f);
1514
	    goto cleanup_and_exit;
1515
	}
Chris Allegretta's avatar
Chris Allegretta committed
1516
#ifdef DEBUG
1517
	else
Chris Allegretta's avatar
Chris Allegretta committed
1518
1519
	    fprintf(stderr, _("Wrote >%s\n"), fileptr->data);
#endif
1520
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1521
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1522
	    putc('\r', f);
Chris Allegretta's avatar
Chris Allegretta committed
1523
1524

	if (!ISSET(MAC_FILE))
1525
#endif
1526
	    putc('\n', f);
Chris Allegretta's avatar
Chris Allegretta committed
1527
1528
1529
1530
1531
1532

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

    if (fileptr != NULL) {
1533
	int data_len = strlen(fileptr->data);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1534
1535
1536
1537

	/* newlines to nulls, just before we write to disk */
	sunder(fileptr->data);

1538
	size = fwrite(fileptr->data, 1, data_len, f);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1539
1540
1541
1542

	/* nulls to newlines; data_len is the string's real length here */
	unsunder(fileptr->data, data_len);

1543
	if (size < data_len) {
Chris Allegretta's avatar
Chris Allegretta committed
1544
1545
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
1546
	    goto cleanup_and_exit;
1547
	} else if (data_len > 0) {
1548
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1549
	    if (ISSET(DOS_FILE) || ISSET(MAC_FILE)) {
1550
		if (putc('\r', f) == EOF) {
1551
1552
		    statusbar(_("Could not open file for writing: %s"),
			  strerror(errno));
1553
		    fclose(f);
1554
		    goto cleanup_and_exit;
1555
		}
1556
		lineswritten++;
1557
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1558
1559

	    if (!ISSET(MAC_FILE))
1560
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1561
	    {
1562
		if (putc('\n', f) == EOF) {
Chris Allegretta's avatar
Chris Allegretta committed
1563
		    statusbar(_("Could not open file for writing: %s"),
Chris Allegretta's avatar
Chris Allegretta committed
1564
			  strerror(errno));
1565
		    fclose(f);
1566
		    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1567
		}
1568
		lineswritten++;
Chris Allegretta's avatar
Chris Allegretta committed
1569
1570
1571
1572
	    }
	}
    }

1573
    if (fclose(f) != 0) {
1574
	statusbar(_("Could not close %s: %s"), realname, strerror(errno));
Chris Allegretta's avatar
Chris Allegretta committed
1575
	unlink(buf);
1576
	goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1577
1578
    }

1579
1580
    /* if we're prepending, open the real file, and append it here */
    if (append == 2) {
1581
1582
1583
	int fd_source, fd_dest;
	FILE *f_source, *f_dest;
	int prechar;
1584

1585
	if ((fd_dest = open(buf, O_WRONLY | O_APPEND, (S_IRUSR | S_IWUSR))) == -1) {
1586
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
1587
	    goto cleanup_and_exit;
1588
	}
1589
	f_dest = fdopen(fd_dest, "wb");
1590
	if (f_dest == NULL) {
1591
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
1592
	    close(fd_dest);
1593
	    goto cleanup_and_exit;
1594
	}
1595
	if ((fd_source = open(realname, O_RDONLY | O_CREAT)) == -1) {
1596
	    statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
1597
	    fclose(f_dest);
1598
	    goto cleanup_and_exit;
1599
1600
	}
	f_source = fdopen(fd_source, "rb");
1601
	if (f_source == NULL) {
1602
1603
1604
	    statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
	    fclose(f_dest);
	    close(fd_source);
1605
	    goto cleanup_and_exit;
1606
1607
	}

1608
1609
1610
1611
1612
1613
        /* Doing this in blocks is an exercise left to some other reader. */
	while ((prechar = getc(f_source)) != EOF) {
	    if (putc(prechar, f_dest) == EOF) {
		statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
		fclose(f_source);
		fclose(f_dest);
1614
		goto cleanup_and_exit;
1615
1616
	    }
	}
1617

1618
1619
1620
1621
	if (ferror(f_source)) {
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
	    fclose(f_source);
	    fclose(f_dest);
1622
	    goto cleanup_and_exit;
1623
1624
1625
1626
	}
	    
	fclose(f_source);
	fclose(f_dest);
1627
1628
    }

1629
    if (realexists == -1 || tmp ||
1630
	(ISSET(NOFOLLOW_SYMLINKS) && S_ISLNK(lst.st_mode))) {
Chris Allegretta's avatar
Chris Allegretta committed
1631

1632
	/* Use default umask as file permissions if file is a new file. */
1633
1634
	mask = umask(0);
	umask(mask);
Chris Allegretta's avatar
Chris Allegretta committed
1635

1636
	if (tmp)	/* We don't want anyone reading our temporary file! */
Chris Allegretta's avatar
Chris Allegretta committed
1637
	    mask = S_IRUSR | S_IWUSR;
1638
	else
Chris Allegretta's avatar
Chris Allegretta committed
1639
1640
	    mask = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH |
		S_IWOTH) & ~mask;
1641
    } else
Chris Allegretta's avatar
Chris Allegretta committed
1642
	/* Use permissions from file we are overwriting. */
1643
1644
	mask = st.st_mode;

1645
    if (append == 2 || 
1646
		(!tmp && (ISSET(NOFOLLOW_SYMLINKS) && S_ISLNK(lst.st_mode)))) {
1647
1648
	if (unlink(realname) == -1) {
	    if (errno != ENOENT) {
1649
		statusbar(_("Could not open %s for writing: %s"),
1650
			  realname, strerror(errno));
1651
		unlink(buf);
1652
		goto cleanup_and_exit;
1653
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1654
	}
1655
1656
1657
1658
1659
1660
	if (link(buf, realname) != -1)
	    unlink(buf);
	else if (errno != EPERM) {
	    statusbar(_("Could not open %s for writing: %s"),
		      name, strerror(errno));
	    unlink(buf);
1661
	    goto cleanup_and_exit;
1662
1663
1664
1665
	} else if (rename(buf, realname) == -1) {	/* Try a rename?? */
	    statusbar(_("Could not open %s for writing: %s"),
		      realname, strerror(errno));
	    unlink(buf);
1666
	    goto cleanup_and_exit;
1667
	}
Chris Allegretta's avatar
Chris Allegretta committed
1668
    }
1669
1670
1671
1672
    if (chmod(realname, mask) == -1)
	statusbar(_("Could not set permissions %o on %s: %s"),
		  mask, realname, strerror(errno));

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

1682
1683
1684
1685
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1686
	statusbar(P_("Wrote %d line", "Wrote %d lines", lineswritten),
1687
			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
1696
1697

    retval = 1;

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

1700
int do_writeout(const char *path, int exiting, int append)
Chris Allegretta's avatar
Chris Allegretta committed
1701
1702
{
    int i = 0;
1703
1704
1705
#ifdef NANO_EXTRA
    static int did_cred = 0;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1706

1707
#if !defined(DISABLE_BROWSER) || (!defined(DISABLE_MOUSE) && defined(NCURSES_MOUSE_VERSION))
1708
    currshortcut = writefile_list;
1709
1710
#endif

1711
    answer = mallocstrcpy(answer, path);
Chris Allegretta's avatar
Chris Allegretta committed
1712

1713
    if (exiting && ISSET(TEMP_OPT)) {
1714
	if (filename[0] != '\0') {
1715
	    i = write_file(answer, 0, 0, 0);
1716
1717
	    display_main_list();
	    return i;
1718
	} else {
Chris Allegretta's avatar
Chris Allegretta committed
1719
1720
1721
1722
1723
	    UNSET(TEMP_OPT);
	    do_exit();

	    /* They cancelled, abort quit */
	    return -1;
1724
	}
Chris Allegretta's avatar
Chris Allegretta committed
1725
1726
1727
    }

    while (1) {
1728
#ifndef NANO_SMALL
1729
1730
	const char *formatstr, *backupstr;

1731
1732
1733
1734
1735
1736
1737
	if (ISSET(MAC_FILE))
	   formatstr = _(" [Mac Format]");
	else if (ISSET(DOS_FILE))
	   formatstr = _(" [DOS Format]");
	else
	   formatstr = "";

1738
1739
1740
1741
1742
	if (ISSET(BACKUP_FILE))
	   backupstr = _(" [Backup]");
	else
	   backupstr = "";

1743
	/* Be nice to the translation folks */
1744
	if (ISSET(MARK_ISSET) && !exiting) {
1745
	    if (append == 2)
1746
		i = statusq(1, writefile_list, "", 0,
1747
		    "%s%s%s", _("Prepend Selection to File"), formatstr, backupstr);
Chris Allegretta's avatar
Chris Allegretta committed
1748
	    else if (append == 1)
1749
		i = statusq(1, writefile_list, "", 0,
1750
		    "%s%s%s", _("Append Selection to File"), formatstr, backupstr);
1751
	    else
1752
		i = statusq(1, writefile_list, "", 0,
1753
1754
		    "%s%s%s", _("Write Selection to File"), formatstr, backupstr);
	} else {
1755
	    if (append == 2)
1756
		i = statusq(1, writefile_list, answer, 0,
1757
		    "%s%s%s", _("File Name to Prepend to"), formatstr, backupstr);
Chris Allegretta's avatar
Chris Allegretta committed
1758
	    else if (append == 1)
1759
		i = statusq(1, writefile_list, answer, 0,
1760
		    "%s%s%s", _("File Name to Append to"), formatstr, backupstr);
1761
	    else
1762
		i = statusq(1, writefile_list, answer, 0,
1763
		    "%s%s%s", _("File Name to Write"), formatstr, backupstr);
1764
	}
1765
1766
#else
	if (append == 2)
Chris Allegretta's avatar
Chris Allegretta committed
1767
	    i = statusq(1, writefile_list, answer,
1768
		"%s", _("File Name to Prepend to"));
Chris Allegretta's avatar
Chris Allegretta committed
1769
1770
	else if (append == 1)
	    i = statusq(1, writefile_list, answer,
1771
1772
		"%s", _("File Name to Append to"));
	else
Chris Allegretta's avatar
Chris Allegretta committed
1773
	    i = statusq(1, writefile_list, answer,
1774
1775
		"%s", _("File Name to Write"));
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1776

1777
1778
1779
1780
1781
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
	    return 0;
	}
Chris Allegretta's avatar
Chris Allegretta committed
1782

1783
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1784
	if (i == NANO_TOFILES_KEY) {
1785
	    char *tmp = do_browse_from(answer);
1786

1787
	    currshortcut = writefile_list;
1788
1789
1790
1791
	    if (tmp == NULL)
		continue;
	    free(answer);
	    answer = tmp;
1792
	} else
1793
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1794
#ifndef NANO_SMALL
1795
1796
1797
	if (i == TOGGLE_DOS_KEY) {
	    UNSET(MAC_FILE);
	    TOGGLE(DOS_FILE);
1798
	    continue;
1799
1800
1801
	} else if (i == TOGGLE_MAC_KEY) {
	    UNSET(DOS_FILE);
	    TOGGLE(MAC_FILE);
1802
	    continue;
1803
1804
	} else if (i == TOGGLE_BACKUP_KEY) {
	    TOGGLE(BACKUP_FILE);
1805
1806
	    continue;
	} else
1807
#endif /* !NANO_SMALL */
1808
1809
1810
1811
1812
1813
1814
	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
1815

Chris Allegretta's avatar
Chris Allegretta committed
1816
#ifdef DEBUG
1817
	fprintf(stderr, _("filename is %s\n"), answer);
Chris Allegretta's avatar
Chris Allegretta committed
1818
#endif
1819
1820

#ifdef NANO_EXTRA
1821
	if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy")
1822
		&& !did_cred) {
1823
1824
1825
1826
	    do_credits();
	    did_cred = 1;
	    return -1;
	}
1827
#endif
1828
	if (append == 0 && strcmp(answer, filename) != 0) {
1829
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1830

1831
1832
1833
1834
1835
	    if (!stat(answer, &st)) {
		i = do_yesno(0, 0, _("File exists, OVERWRITE ?"));

		if (i == 0 || i == -1)
		    continue;
Chris Allegretta's avatar
Chris Allegretta committed
1836
	    }
1837
	}
Chris Allegretta's avatar
Chris Allegretta committed
1838

1839
#ifndef NANO_SMALL
1840
1841
1842
	/* Here's where we allow the selected text to be written to 
	   a separate file. */
	if (ISSET(MARK_ISSET) && !exiting) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1843
	    filestruct *fileagebak = fileage;
1844
1845
	    filestruct *filebotbak = filebot;
	    filestruct *cutback = cutbuffer;
1846
1847
	    int oldmod = ISSET(MODIFIED);
		/* write_file() unsets the MODIFIED flag. */
1848

1849
	    cutbuffer = NULL;
1850

1851
1852
1853
	    /* Put the marked text in the cutbuffer without changing
	       the open file. */
	    cut_marked_segment(current, current_x, mark_beginbuf,
1854
1855
1856
				mark_beginx, 0);

	    fileage = cutbuffer;
1857
	    filebot = get_cutbottom();
1858
	    i = write_file(answer, 0, append, 1);
1859
1860

	    /* Now restore everything */
1861
	    free_filestruct(cutbuffer);
1862
1863
1864
1865
1866
1867
	    fileage = fileagebak;
	    filebot = filebotbak;
	    cutbuffer = cutback;
	    if (oldmod)
		set_modified();
	} else
1868
#endif /* !NANO_SMALL */
1869
	    i = write_file(answer, 0, append, 0);
1870

1871
#ifdef ENABLE_MULTIBUFFER
1872
1873
1874
1875
	/* If we're not about to exit, update the current entry in
	   the open_files structure. */
	if (!exiting)
	    add_open_file(1);
1876
#endif
1877
1878
1879
	display_main_list();
	return i;
    } /* while (1) */
Chris Allegretta's avatar
Chris Allegretta committed
1880
1881
1882
1883
}

int do_writeout_void(void)
{
1884
    return do_writeout(filename, 0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
1885
}
Chris Allegretta's avatar
Chris Allegretta committed
1886

1887
/* Return a malloc()ed string containing the actual directory, used
1888
1889
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1890
{
Chris Allegretta's avatar
Chris Allegretta committed
1891
    char *dirtmp = NULL;
1892

1893
    if (buf[0] == '~') {
1894
1895
1896
1897
1898
1899
1900
	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++)
	    ;

Chris Allegretta's avatar
Chris Allegretta committed
1901
	/* Determine home directory using getpwent(), don't rely on
Chris Allegretta's avatar
Chris Allegretta committed
1902
	       $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
1903
1904
1905
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
1906
1907
1908
1909
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
			strncmp(userdata->pw_name, buf + 1, i - 1));
Chris Allegretta's avatar
Chris Allegretta committed
1910
1911
	}
	endpwent();
1912

1913
	if (userdata != NULL) {	/* User found */
1914
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1915
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1916
	}
1917
    }
1918

1919
1920
1921
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1922
    return dirtmp;
1923
1924
}

Chris Allegretta's avatar
Chris Allegretta committed
1925
#ifndef DISABLE_TABCOMP
1926
1927
1928
/* 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. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1929
int append_slash_if_dir(char *buf, int *lastwastab, int *place)
1930
{
1931
    char *dirptr = real_dir_from_tilde(buf);
1932
    struct stat fileinfo;
1933
    int ret = 0;
1934

1935
    assert(dirptr != buf);
1936

1937
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
1938
	strncat(buf, "/", 1);
1939
	(*place)++;
1940
	/* now we start over again with # of tabs so far */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1941
	*lastwastab = 0;
1942
	ret = 1;
1943
1944
    }

1945
    free(dirptr);
1946
    return ret;
1947
}
Chris Allegretta's avatar
Chris Allegretta committed
1948
1949

/*
Chris Allegretta's avatar
Chris Allegretta committed
1950
1951
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1952
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
 *
 * 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)
{
1970
    char **matches = (char **)NULL;
1971
    char *matchline = NULL;
1972
    struct passwd *userdata;
1973

1974
    *num_matches = 0;
1975
    matches = nmalloc(BUFSIZ * sizeof(char *));
1976

1977
    strcat(buf, "*");
1978

1979
    while ((userdata = getpwent()) != NULL) {
1980

1981
	if (check_wildcard_match(userdata->pw_name, &buf[1]) == TRUE) {
1982
1983
1984
1985

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

1987
1988
1989
1990
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

1991
1992
	    if (operating_dir != NULL) {
		if (check_operating_dir(userdata->pw_dir, 1) != 0)
1993
1994
1995
1996
		    continue;
	    }
#endif

1997
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
1998
1999
2000
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
	    ++*num_matches;
2001

2002
2003
2004
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2005
	}
2006
2007
    }
    endpwent();
2008

2009
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2010
2011
2012
2013
2014
2015
2016
}

/* 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
2017
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2018
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2019
2020
2021
    DIR *dir;
    struct dirent *next;

2022
    matches = nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2023
2024
2025
2026

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

2027
    /* Okie, if there's a / in the buffer, strip out the directory part */
2028
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2029
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2030
2031
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2032
	    tmp--;
2033

Chris Allegretta's avatar
Chris Allegretta committed
2034
2035
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2036
2037
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2038

Chris Allegretta's avatar
Chris Allegretta committed
2039
    } else {
2040
2041

#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2042
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
2043
#else
2044
	/* The better, but apparently segfault-causing way */
Chris Allegretta's avatar
Chris Allegretta committed
2045
	if ((dirname = getcwd(NULL, 0)) == NULL)
2046
#endif /* PATH_MAX */
Chris Allegretta's avatar
Chris Allegretta committed
2047
2048
2049
2050
2051
2052
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2053
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2054
2055
2056
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2057

Chris Allegretta's avatar
Chris Allegretta committed
2058
2059
2060
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2061
2062

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2063
    fprintf(stderr, "\nDir = %s\n", dirname);
2064
2065
2066
2067
2068
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2069
    dir = opendir(dirname);
2070
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2071
2072
2073
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2074
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2075
2076
2077
2078
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2079
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2080
2081
2082
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
2083
2084
2085
2086

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2087
2088
2089
2090
2091
2092
2093
2094

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

2095
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2096
2097
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
		if (check_operating_dir(tmp2, 1)) {
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2108
	    tmp2 = NULL;
2109
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2110
2111
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2112
	    ++*num_matches;
2113
2114
2115
2116

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2117
2118
	}
    }
2119
2120
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2121

2122
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2123
2124
}

Chris Allegretta's avatar
Chris Allegretta committed
2125
2126
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2127
char *input_tab(char *buf, int place, int *lastwastab, int *newplace, int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2128
2129
{
    /* Do TAB completion */
2130
    static int num_matches = 0, match_matches = 0;
2131
    static char **matches = (char **)NULL;
2132
    int pos = place, i = 0, col = 0, editline = 0;
2133
    int longestname = 0, is_dir = 0;
2134
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2135

2136
2137
    *list = 0;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2138
    if (*lastwastab == FALSE) {
2139
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2140

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2141
	*lastwastab = 1;
2142

Chris Allegretta's avatar
Chris Allegretta committed
2143
2144
	/* Make a local copy of the string -- up to the position of the
	   cursor */
2145
2146
	matchbuf = (char *)nmalloc((strlen(buf) + 2) * sizeof(char));
	memset(matchbuf, '\0', (strlen(buf) + 2));
2147

2148
2149
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2150
2151

	/* skip any leading white space */
2152
	while (*tmp && isspace((int)*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2153
2154
2155
2156
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2157
2158
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2159
	    free(matches);
2160
	    matches = (char **)NULL;
2161
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2162
2163
2164
2165
2166
	}

	/* 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
2167
2168
2169
2170
2171
	/* 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. */
2172
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2173
	    buf = mallocstrcpy(buf, tmp);
2174
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2175
	}
2176
2177
2178
2179
2180
2181
	/* 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
2182
2183
2184

	/* Try to match everything in the current working directory that
	 * matches.  */
2185
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2186
2187
2188
	    matches = cwd_tab_completion(tmp, &num_matches);

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

2191
2192
2193
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2194
	/* Did we find exactly one match? */
2195
	switch (num_matches) {
2196
	case 0:
2197
	    blank_edit();
2198
	    wrefresh(edit);
2199
2200
	    break;
	case 1:
2201
2202
2203

	    buf = nrealloc(buf, strlen(buf) + strlen(matches[0]) + 1);

2204
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2205
2206
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2207
		tmp++;
2208
	    } else
2209
2210
		tmp = buf;

2211
	    if (!strcmp(tmp, matches[0]))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2212
		is_dir = append_slash_if_dir(buf, lastwastab, newplace);
2213

2214
	    if (is_dir != 0)
2215
		break;
2216
2217

	    copyto = tmp;
2218
2219
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2220
2221
		tmp++;

2222
	    /* write out the matched name */
2223
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2224
2225
	    *newplace += strlen(matches[0]) - pos;

2226
2227
2228
2229
2230
2231
2232
	    /* 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;

2233
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2234
	    append_slash_if_dir(buf, lastwastab, newplace);
2235

2236
2237
	    break;
	default:
2238
	    /* Check to see if all matches share a beginning, and, if so,
2239
	       tack it onto buf and then beep */
2240

2241
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2242
2243
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2244
		tmp++;
2245
	    } else
2246
2247
		tmp = buf;

2248
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2249
		 pos <= strlen(matches[0]); pos++)
2250
2251
		tmp++;

2252
2253
2254
2255
2256
2257
2258
2259
2260
	    while (1) {
		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++;
		}
2261
2262
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2263
		    /* All the matches have the same character at pos+1,
2264
		       so paste it into buf... */
2265
		    buf = nrealloc(buf, strlen(buf) + 2);
2266
		    strncat(buf, matches[0] + pos, 1);
2267
		    *newplace += 1;
2268
		    pos++;
2269
		} else {
2270
2271
2272
2273
		    beep();
		    break;
		}
	    }
2274
	    break;
2275
	}
Chris Allegretta's avatar
Chris Allegretta committed
2276
2277
2278
2279
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2280
	if (matches != NULL && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2281
2282
2283
2284
2285

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

2286
	    editline = 0;
2287

2288
2289
2290
2291
2292
2293
2294
2295
	    /* 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;

2296
	    foo = charalloc(longestname + 5);
2297

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

2301
		/* make each filename shown be the same length as the longest
2302
		   filename, with two spaces at the end */
2303
2304
2305
2306
2307
2308
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2309
2310
		/* Disable el cursor */
		curs_set(0);
2311
2312
2313
2314
2315
2316
		/* 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 */
2317
		if (col > COLS - longestname && i + 1 < num_matches) {
2318
2319
		    editline++;
		    wmove(edit, editline, 0);
2320
2321
2322
2323
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2324
2325
2326
		    col = 0;
		}
	    }
2327
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2328
	    wrefresh(edit);
2329
	    *list = 1;
2330
2331
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2332
2333
    }

2334
2335
2336
2337
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
    if (*list == 0)
	edit_refresh();
2338
2339
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2340
}
2341
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2342

2343
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
2344
/* Return the stat of the file pointed to by path */
Chris Allegretta's avatar
Chris Allegretta committed
2345
2346
struct stat filestat(const char *path)
{
Chris Allegretta's avatar
Chris Allegretta committed
2347
2348
    struct stat st;

2349
    stat(path, &st);
Chris Allegretta's avatar
Chris Allegretta committed
2350
2351
2352
2353
    return st;
}

/* Our sort routine for file listings - sort directories before
2354
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2355
2356
int diralphasort(const void *va, const void *vb)
{
2357
2358
2359
2360
    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
2361

2362
    if (aisdir != 0 && bisdir == 0)
2363
	return -1;
2364
    if (aisdir == 0 && bisdir != 0)
2365
	return 1;
2366

2367
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2368
2369
}

2370
/* Free our malloc()ed memory */
Chris Allegretta's avatar
Chris Allegretta committed
2371
2372
void free_charptrarray(char **array, int len)
{
2373
2374
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2375
2376
2377
    free(array);
}

2378
2379
2380
/* Only print the last part of a path; isn't there a shell 
 * command for this? */
const char *tail(const char *foo)
Chris Allegretta's avatar
Chris Allegretta committed
2381
{
2382
    const char *tmp = foo + strlen(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2383
2384
2385
2386

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

2387
2388
    if (*tmp == '/')
	tmp++;
Chris Allegretta's avatar
Chris Allegretta committed
2389
2390
2391
2392

    return tmp;
}

2393
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2394
2395
void striponedir(char *foo)
{
2396
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2397

2398
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2399
    /* Don't strip the root dir */
2400
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2401
2402
	return;

2403
2404
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2405
    if (*tmp == '/')
2406
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2407
2408
2409
2410
2411

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

    if (tmp != foo)
2412
2413
2414
2415
2416
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2417
    }
Chris Allegretta's avatar
Chris Allegretta committed
2418
2419
}

2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
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;
}

2430
/* Initialize the browser code, including the list of files in *path */
2431
char **browser_init(const char *path, int *longest, int *numents)
2432
2433
2434
{
    DIR *dir;
    struct dirent *next;
2435
    char **filelist;
2436
    int i = 0;
2437
    size_t path_len;
2438
2439

    dir = opendir(path);
2440
    if (dir == NULL)
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;
	(*numents)++;
	if (strlen(next->d_name) > *longest)
	    *longest = strlen(next->d_name);
    }
    rewinddir(dir);
    *longest += 10;

    filelist = nmalloc(*numents * sizeof (char *));

2456
2457
2458
2459
    if (!strcmp(path, "/"))
	path = "";
    path_len = strlen(path);

2460
2461
2462
2463
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;

2464
2465
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2466
2467
	i++;
    }
2468
    closedir(dir);
2469
2470
2471
2472
2473
2474
2475

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2476
/* Our browser function.  inpath is the path to start browsing from */
2477
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2478
2479
2480
2481
2482
2483
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
    int numents = 0, i = 0, j = 0, kbinput = 0, longest = 0, abort = 0;
    int col = 0, selected = 0, editline = 0, width = 0, filecols = 0;
2484
    int lineno = 0, kb;
2485
2486
    char **filelist = (char **)NULL;
#if !defined(DISABLE_MOUSE) && defined(NCURSES_MOUSE_VERSION)
2487
2488
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2489

2490
2491
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2492
2493
2494
    /* 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 */
Chris Allegretta's avatar
Chris Allegretta committed
2495
2496
2497
2498
2499
    if (path != NULL && strcmp(path, inpath)) {
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2500
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2501
    if (path == NULL)
2502
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2503
2504

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

2507
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2508
2509
    qsort(filelist, numents, sizeof(char *), diralphasort);

2510
    kb = keypad_on(edit, 1);
Chris Allegretta's avatar
Chris Allegretta committed
2511
    titlebar(path);
2512
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2513
2514
2515
2516
2517
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2518
2519

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2520
    do {
2521
2522
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2523
2524
2525

	blank_statusbar_refresh();

2526
#if !defined(DISABLE_HELP) || (!defined(DISABLE_MOUSE) && defined(NCURSES_MOUSE_VERSION))
2527
	currshortcut = browser_list;
2528
2529
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2530
2531
 	editline = 0;
	col = 0;
2532
	    
2533
	/* Compute line number we're on now, so we don't divide by zero later */
2534
2535
2536
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2537
2538

	switch (kbinput) {
2539

2540
#if !defined(DISABLE_MOUSE) && defined(NCURSES_MOUSE_VERSION)
2541
	case KEY_MOUSE:
2542
	    if (getmouse(&mevent) == ERR)
2543
		return retval;
2544
2545
2546
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2547
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2548
2549
2550
2551
		int selectedbackup = selected;

		mevent.y -= 2;

2552
2553
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2554
		selected = (lineno / editwinrows) * editwinrows * width
2555
2556
2557
2558
2559
2560
			+ 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--;
2561
2562
2563

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2564
		if (selected > numents - 1)
2565
		    selected = numents - 1;
2566
		else if (selectedbackup == selected)
2567
2568
2569
2570
		    ungetch('s');	/* Unget the 'select' key */
	    } else	/* Must be clicking a shortcut */
		do_mouse();

2571
2572
            break;
#endif
2573
	case NANO_UP_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2574
2575
2576
2577
2578
	case KEY_UP:
	case 'u':
	    if (selected - width >= 0)
		selected -= width;
	    break;
2579
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2580
	case KEY_LEFT:
2581
2582
	case NANO_BACKSPACE_KEY:
	case 127:
Chris Allegretta's avatar
Chris Allegretta committed
2583
2584
2585
2586
2587
	case 'l':
	    if (selected > 0)
		selected--;
	    break;
	case KEY_DOWN:
2588
	case NANO_DOWN_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2589
2590
2591
2592
2593
	case 'd':
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
	case KEY_RIGHT:
2594
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2595
2596
2597
2598
2599
	case 'r':
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2600
	case NANO_PREVPAGE_FKEY:
Chris Allegretta's avatar
Chris Allegretta committed
2601
	case KEY_PPAGE:
2602
	case '-':
2603
	    if (selected >= (editwinrows + lineno % editwinrows) * width)
2604
		selected -= (editwinrows + lineno % editwinrows) * width; 
Chris Allegretta's avatar
Chris Allegretta committed
2605
2606
2607
2608
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2609
	case NANO_NEXTPAGE_FKEY:
Chris Allegretta's avatar
Chris Allegretta committed
2610
	case KEY_NPAGE:	
2611
	case ' ':
2612
2613
	    selected += (editwinrows - lineno % editwinrows) * width;
	    if (selected >= numents)
Chris Allegretta's avatar
Chris Allegretta committed
2614
2615
		selected = numents - 1;
	    break;
2616
2617
2618
2619
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
	     do_help();
	     break;
Chris Allegretta's avatar
Chris Allegretta committed
2620
	case KEY_ENTER:
2621
	case NANO_ENTER_KEY:
2622
2623
	case 's': /* More Pico compatibility */
	case 'S':
Chris Allegretta's avatar
Chris Allegretta committed
2624
	    /* You can't cd up from / */
Rocco Corsi's avatar
   
Rocco Corsi committed
2625
	    if (!strcmp(filelist[selected], "/..") && !strcmp(path, "/")) {
Chris Allegretta's avatar
Chris Allegretta committed
2626
		statusbar(_("Can't move up a directory"));
2627
		beep();
Rocco Corsi's avatar
   
Rocco Corsi committed
2628
2629
2630
		break;
	    }

2631
#ifndef DISABLE_OPERATINGDIR
2632
2633
2634
2635
2636
2637
2638
2639
	    /*
	     *  Note: the selected file can be outside the operating
	     *  directory if it is .. or if it is a symlink to a directory
	     *  outside the opdir. */
	    if (check_operating_dir(filelist[selected], FALSE)) {
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		beep();
		break;
2640
2641
2642
	    }
#endif

2643
2644
2645
2646
2647
	    if (stat(filelist[selected], &st) == -1) {
		statusbar(_("Can't open \"%s\": %s"), filelist[selected], strerror(errno));
		beep();
		break;
	    }
2648

2649
2650
2651
2652
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2653
2654
	    }

2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
	    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
2665
		}
2666
2667
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2668

2669
2670
2671
2672
2673
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), new_path, strerror(errno));
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2674
	    }
2675
2676
2677
2678
2679
2680
2681

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

Rocco Corsi's avatar
   
Rocco Corsi committed
2682
2683
2684
2685
2686
2687
	/* Goto a specific directory */
	case 'g':	/* Pico compatibility */
	case 'G':
	case NANO_GOTO_KEY:

	    curs_set(1);
Chris Allegretta's avatar
Chris Allegretta committed
2688
2689
2690
2691
2692
	    j = statusq(0, gotodir_list, "",
#ifndef NANO_SMALL
		0,
#endif
		_("Goto Directory"));
2693
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2694
2695
2696
2697
2698
2699
2700
	    curs_set(0);

	    if (j < 0) {
		statusbar(_("Goto Cancelled"));
		break;
	    }

2701
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2702

2703
2704
2705
	    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
2706
2707
	    }

2708
2709
2710
2711
2712
2713
2714
2715
2716
#ifndef DISABLE_OPERATINGDIR
	    if (check_operating_dir(new_path, FALSE)) {
		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
2717
2718
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2719
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2720
		break;
2721
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2722
2723

	    /* Start over again with the new path value */
2724
2725
	    free_charptrarray(filelist, numents);
	    free(foo);
2726
2727
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2728
2729
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2730
2731
2732
2733
2734
	/* Stuff we want to abort the browser */
	case 'q':
	case 'Q':
	case 'e':	/* Pico compatibility, yeech */
	case 'E':
2735
	case NANO_CANCEL_KEY:
2736
	case NANO_EXIT_FKEY:
2737
2738
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2739
2740
2741
2742
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2743
2744
	blank_edit();

2745
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
	    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 */
2760
2761
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2762
2763
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2764
2765
2766
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2767
2768
2769
2770
2771
2772
2773
2774
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
		    st = filestat(filelist[j]);
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2775
2776
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2777
			(int) st.st_size);
2778
2779
2780
2781
2782
2783
		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);
2784
		else /* It's more than 1 k and less than a meg */
2785
2786
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2787
2788
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2789
	    /* Highlight the currently selected file/dir */
2790
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2791
		wattron(edit, A_REVERSE);
2792
2793
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2794
2795
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2796
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2797
2798
2799
2800
2801
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2802
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
 	wrefresh(edit);
    } while ((kbinput = wgetch(edit)) != NANO_EXIT_KEY);
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2814
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2815
    edit_refresh();
2816
    kb = keypad_on(edit, kb);
Chris Allegretta's avatar
Chris Allegretta committed
2817

Chris Allegretta's avatar
Chris Allegretta committed
2818
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2819
2820
2821
2822
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2823

2824
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2825
 starts do_browser from there, else from the current dir */
2826
char *do_browse_from(const char *inpath)
2827
2828
{
    struct stat st;
2829
    char *bob;
2830
2831
2832
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2833

2834
2835
2836
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2837

2838
2839
2840
2841
2842
2843
2844
2845
    /*
     * 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);
2846
2847
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2848
	    path = getcwd(NULL, PATH_MAX + 1);
2849
	}
2850
    }
2851

2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
    if (check_operating_dir(path, FALSE))
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2864
    return bob;
2865
}
Chris Allegretta's avatar
Chris Allegretta committed
2866
#endif /* !DISABLE_BROWSER */
2867
2868
2869
2870
2871
2872

#ifndef NANO_SMALL
#ifdef ENABLE_NANORC
void load_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2873
    const struct passwd *userage = NULL;
2874
2875
    static char *nanohist;
    char *buf, *ptr;
Chris Allegretta's avatar
Chris Allegretta committed
2876
    char *homenv = getenv("HOME");
2877
2878
2879
    historyheadtype *history = &search_history;


Chris Allegretta's avatar
Chris Allegretta committed
2880
2881
2882
2883
2884
2885
    if (homenv != NULL) {
        nanohist = nrealloc(nanohist, strlen(homenv) + 15);
        sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
2886
2887
        nanohist = nrealloc(nanohist, strlen(userage->pw_dir) + 15);
        sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2888
2889
2890
2891
2892
    }

    /* assume do_rcfile has reported missing home dir */

    if (homenv != NULL || userage != NULL) {
2893
2894
2895
2896
2897
2898
2899
2900
2901
	hist = fopen(nanohist, "r");
	if (!hist) {
            if (errno != ENOENT)
		rcfile_error(_("Unable to open ~/.nano_history file, %s"), strerror(errno));
	    free(nanohist);
	} else {
	    buf = charalloc(1024);
	    while (fgets(buf, 1023, hist) != 0) {
		ptr = buf;
2902
		while (*ptr != '\n' && *ptr != '\0' && ptr < buf + 1023)
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
		    ptr++;
		*ptr = '\0';
		if (strlen(buf))
		    update_history(history, buf);
		else
		    history = &replace_history;
	    }
	    fclose(hist);
	    free(buf);
	    free(nanohist);
	    UNSET(HISTORY_CHANGED);
	}
    }
}

/* save histories to ~/.nano_history */
void save_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2922
    const struct passwd *userage = NULL;
2923
    char *nanohist = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2924
    char *homenv = getenv("HOME");
2925
2926
2927
2928
2929
2930
2931
    historytype *h;

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

Chris Allegretta's avatar
Chris Allegretta committed
2932
2933
2934
2935
2936
2937
    if (homenv != NULL) {
	nanohist = nrealloc(nanohist, strlen(homenv) + 15);
	sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
2938
2939
	nanohist = nrealloc(nanohist, strlen(userage->pw_dir) + 15);
	sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2940
2941
2942
    }

    if (homenv != NULL || userage != NULL) {
2943
2944
2945
2946
2947
2948
2949
2950
	hist = fopen(nanohist, "wb");
	if (!hist) {
	    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
	} else {
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
	    /* write oldest first */
	    for (h = search_history.tail ; h->prev ; h = h->prev) {
2951
		h->data = nrealloc(h->data, strlen(h->data) + 2);
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
		    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
		    goto come_from;
		}
	    }
	    if (fputs("\n", hist) == EOF) {
		    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
		    goto come_from;
	    }
	    for (h = replace_history.tail ; h->prev ; h = h->prev) {
2963
		h->data = nrealloc(h->data, strlen(h->data) + 2);
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
		    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
		    goto come_from;
		}
	    }
come_from:
	    fclose(hist);
	}
	free(nanohist);
    }
}
#endif /* ENABLE_NANORC */
Chris Allegretta's avatar
Chris Allegretta committed
2977
#endif /* !NANO_SMALL */