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

22
23
#include "config.h"

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

40
41
42
43
44
45
/* 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

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

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

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

62
63
#ifdef ENABLE_COLOR
    update_color();
64
65
    if (ISSET(COLOR_SYNTAX))
	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
    filebot = fileage;
    edittop = fileage;
    current = fileage;
78
    current_x = 0;
Chris Allegretta's avatar
Chris Allegretta committed
79
    totlines = 1;
80
    totsize = 0;
81
82
83

#ifdef ENABLE_MULTIBUFFER
    /* if there aren't any entries in open_files, create the entry for
84
85
       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
86
       given no open_files entry */
87
    if (open_files == NULL) {
88
	add_open_file(FALSE);
89
90
91
92
93
94
95
96
97
	/* 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);
98
99
#endif

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

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

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

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

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

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

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

131
    if (*line1ins != 0 || fileage == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
132
133
134
135
	/* Special case, insert with cursor on 1st line. */
	fileptr->prev = NULL;
	fileptr->next = fileage;
	fileptr->lineno = 1;
136
137
138
139
140
141
142
143
	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
144
	fileage = fileptr;
145
146
    } else {
	assert(prev != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
147
148
149
150
151
152
153
154
155
	fileptr->prev = prev;
	fileptr->next = NULL;
	fileptr->lineno = prev->lineno + 1;
	prev->next = fileptr;
    }

    return fileptr;
}

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

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

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

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

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

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

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

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

205
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
206
	    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
207
	    i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
208
#ifndef NANO_SMALL
209
210
	/* 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
211
	} else if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r') {
212
	    fileformat = 2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
213
214
215
216

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

217
218
219
220
	    /* 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
221

Chris Allegretta's avatar
Chris Allegretta committed
222
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
223
	    totsize++;
224
	    buf[0] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
225
	    buf[1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
226
227
	    i = 1;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
228
	} else {
229
230
231
232
233

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

249
250
251
252
253
    /* 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
254
255

    /* Did we not get a newline but still have stuff to do? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
256
    if (len > 0) {
257
258
259
260
#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 */
261
	if (!ISSET(NO_CONVERT) && buf[len - 1] == '\r' && fileformat == 0)
262
263
264
	    fileformat = 2;
#endif

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

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

287
288
289
290
291
292
293
294
295
    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

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

300
    /* Did we try to insert a file of 0 bytes? */
301
302
303
304
305
306
307
308
309
310
311
312
313
    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
314
    }
315
316
317

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

330
331
332
333
334
335
#ifndef NANO_SMALL
    /* Set fileformat back to 0, now that we've read the file in and
       possibly converted it from DOS/Mac format. */
    fileformat = 0;
#endif

336
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
337

338
    return;
Chris Allegretta's avatar
Chris Allegretta committed
339
340
}

341
342
343
/* Open the file (and decide if it exists).  Return TRUE on success,
 * FALSE on failure. */
bool open_file(const char *filename, int insert, int quiet)
Chris Allegretta's avatar
Chris Allegretta committed
344
345
{
    int fd;
346
    FILE *f;
Chris Allegretta's avatar
Chris Allegretta committed
347
348
    struct stat fileinfo;

Chris Allegretta's avatar
Chris Allegretta committed
349
    if (filename[0] == '\0' || stat(filename, &fileinfo) == -1) {
350
351
	if (insert && !quiet) {
	    statusbar(_("\"%s\" not found"), filename);
352
	    return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
353
354
355
356
357
	} else {
	    /* We have a new file */
	    statusbar(_("New File"));
	    new_file();
	}
358
359
360
361
362
363
364
    } 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();
365
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
366
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
Chris Allegretta's avatar
Chris Allegretta committed
367
368
369
370
371
372
373
	/* 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
		)
374
	    statusbar("Error reading %s: %s", filename, strerror(errno));
375
376
	if (!insert)
	    new_file();
377
	return FALSE;
Chris Allegretta's avatar
Chris Allegretta committed
378
379
380
    } else {			/* File is A-OK */
	if (!quiet)
	    statusbar(_("Reading File"));
381
	f = fdopen(fd, "rb"); /* Binary for our own line-end munging */
382
	if (f == NULL) {
383
	    nperror("fdopen");
384
	    close(fd);
385
	    return FALSE;
386
387
	}
	read_file(f, filename, quiet);
388
389
390
#ifndef NANO_SMALL
	stat(filename, &originalfilestat);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
391
392
    }

393
    return TRUE;
Chris Allegretta's avatar
Chris Allegretta committed
394
395
}

396
/* This function will return the name of the first available extension
397
398
399
 * of a filename (starting with the filename.save, then filename.save.1,
 * etc).  Memory is allocated for the return value.  If no writable
 * extension exists, we return "". */
Chris Allegretta's avatar
Chris Allegretta committed
400
char *get_next_filename(const char *name)
401
402
{
    int i = 0;
403
404
    char *buf;
    size_t namelen = strlen(name);
405

406
    buf = charalloc(namelen + num_of_digits(INT_MAX) + 7);
407
    strcpy(buf, name);
408
409
    strcpy(buf + namelen, ".save");
    namelen += 5;
410

411
    while (TRUE) {
412
	struct stat fs;
413
414

	if (stat(buf, &fs) == -1)
Chris Allegretta's avatar
Chris Allegretta committed
415
	    return buf;
416
417
418
419
	if (i == INT_MAX)
	    break;

	i++;
420
	sprintf(buf + namelen, ".%d", i);
421
422
    }

Chris Allegretta's avatar
Chris Allegretta committed
423
424
    /* We get here only if there is no possible save file. */
    buf[0] = '\0';
425
426
427
428

    return buf;
}

429
void do_insertfile(int loading_file)
Chris Allegretta's avatar
Chris Allegretta committed
430
{
Chris Allegretta's avatar
Chris Allegretta committed
431
    int i, old_current_x = current_x;
432
433
    bool opened;
	/* TRUE if the file opened successfully. */
434
    char *realname = NULL;
435
436
    static char *inspath = NULL;

437
438
439
440
    if (inspath == NULL) {
	inspath = charalloc(1);
	inspath[0] = '\0';
    }
Chris Allegretta's avatar
Chris Allegretta committed
441

442
#ifndef DISABLE_WRAPPING
Chris Allegretta's avatar
Chris Allegretta committed
443
    wrap_reset();
444
#endif
445

446
  start_again:
447
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
448
    currshortcut = insertfile_list;
449
450
#endif

451
#ifndef DISABLE_OPERATINGDIR
452
    if (operating_dir != NULL && strcmp(operating_dir, ".") != 0)
453
454
#ifdef ENABLE_MULTIBUFFER 
	if (ISSET(MULTIBUFFER))
455
	    i = statusq(TRUE, insertfile_list, inspath,
Chris Allegretta's avatar
Chris Allegretta committed
456
#ifndef NANO_SMALL
457
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
458
459
#endif
		_("File to insert into new buffer [from %s] "),
460
461
462
		operating_dir);
	else
#endif
463
	    i = statusq(TRUE, insertfile_list, inspath,
Chris Allegretta's avatar
Chris Allegretta committed
464
#ifndef NANO_SMALL
465
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
466
467
#endif
		_("File to insert [from %s] "),
468
		operating_dir);
469

470
    else
471
#endif
472
473
#ifdef ENABLE_MULTIBUFFER 
	if (ISSET(MULTIBUFFER))
474
	    i = statusq(TRUE, insertfile_list, inspath,
Chris Allegretta's avatar
Chris Allegretta committed
475
#ifndef NANO_SMALL
476
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
477
478
#endif
		_("File to insert into new buffer [from ./] "));
479
	else
Chris Allegretta's avatar
Chris Allegretta committed
480
#endif /* ENABLE_MULTIBUFFER */
481
	    i = statusq(TRUE, insertfile_list, inspath,
Chris Allegretta's avatar
Chris Allegretta committed
482
#ifndef NANO_SMALL
483
		NULL,
484
#endif
Chris Allegretta's avatar
Chris Allegretta committed
485
		_("File to insert [from ./] "));
486

Chris Allegretta's avatar
Chris Allegretta committed
487
    if (i != -1) {
488
	inspath = mallocstrcpy(inspath, answer);
Chris Allegretta's avatar
Chris Allegretta committed
489
#ifdef DEBUG
490
	fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
491
492
#endif

493
#ifndef NANO_SMALL
494
#ifdef ENABLE_MULTIBUFFER
495
	if (i == TOGGLE_MULTIBUFFER_KEY) {
496
497
	    /* Don't allow toggling if we're in view mode. */
	    if (!ISSET(VIEW_MODE))
498
		TOGGLE(MULTIBUFFER);
499
500
	    loading_file = ISSET(MULTIBUFFER);
	    goto start_again;
501
	}
502
#endif /* ENABLE_MULTIBUFFER */
503

504
	if (i == NANO_EXTCMD_KEY) {
505
506
	    char *ans = mallocstrcpy(NULL, answer);
	    int ts = statusq(TRUE, extcmd_list, ans, NULL, 
507
		_("Command to execute"));
508
509
510

	    free(ans);

511
	    if (ts  == -1 || answer == NULL || answer[0] == '\0') {
512
		statusbar(_("Cancelled"));
513
		display_main_list();
514
		return;
515
516
	    }
	}
517
#endif /* !NANO_SMALL */
518
519
520
521
#ifndef DISABLE_BROWSER
	if (i == NANO_TOFILES_KEY) {
	    char *tmp = do_browse_from(answer);

522
523
	    if (tmp != NULL) {
		free(answer);
524
		answer = tmp;
525
		resetstatuspos = 1;
526
	    } else
527
528
529
530
531
		goto start_again;
	}
#endif

#ifndef DISABLE_OPERATINGDIR
532
533
534
535
	if (
#ifndef NANO_SMALL
		i != NANO_EXTCMD_KEY &&
#endif
536
		check_operating_dir(answer, FALSE) != 0) {
537
	    statusbar(_("Can't insert file from outside of %s"),
538
		operating_dir);
539
	    return;
540
541
	}
#endif
542

543
#ifdef ENABLE_MULTIBUFFER
544
	if (loading_file) {
545
	    /* update the current entry in the open_files structure */
546
	    add_open_file(TRUE);
547
548
	    new_file();
	    UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
549
550
551
#ifndef NANO_SMALL
	    UNSET(MARK_ISSET);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
552
553
554
	}
#endif

555
#ifndef NANO_SMALL
556
557
	if (i == NANO_EXTCMD_KEY) {
	    realname = mallocstrcpy(realname, "");
558
	    opened = open_pipe(answer);
559
560
	} else {
#endif
561
	    realname = real_dir_from_tilde(answer);
562
	    opened = open_file(realname, TRUE, loading_file);
563
#ifndef NANO_SMALL
564
	}
565
#endif
566

567
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
568
569
570
571
572
573
	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 */
574
	    if (!opened) {
Chris Allegretta's avatar
Chris Allegretta committed
575
576
577
578
579
580
581
		free(realname);
		free(fileage);
		load_open_file();
		goto skip_insert;
	    } else
		filename = mallocstrcpy(filename, realname);
	}
582
583
#endif

584
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
585

Chris Allegretta's avatar
Chris Allegretta committed
586
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
587
	dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
588
#endif
589

590
#ifdef ENABLE_MULTIBUFFER
591
	if (loading_file)
592
	    load_file(FALSE);
593
594
595
	else
#endif
	    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
596

597
#ifdef ENABLE_MULTIBUFFER
598
599
600
601
602
603
	/* 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 */
604
	    shortcut_init(FALSE);
605
	} else
Chris Allegretta's avatar
Chris Allegretta committed
606
607
608
609
#endif
	    /* Restore the old x-coordinate position */
	    current_x = old_current_x;

610
	/* If we've gone off the bottom, recenter; otherwise, just redraw */
611
	edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
612
613
614

    } else {
	statusbar(_("Cancelled"));
615
	i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
616
    }
617

Chris Allegretta's avatar
Chris Allegretta committed
618
619
620
621
#ifdef ENABLE_MULTIBUFFER
  skip_insert:
#endif

622
623
624
625
    free(inspath);
    inspath = NULL;

    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
626
627
}

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

    display_main_list();
}

646
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
647
648
649
/* Create a new openfilestruct node. */
openfilestruct *make_new_opennode(openfilestruct *prevnode)
{
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
650
    openfilestruct *newnode = (openfilestruct *)nmalloc(sizeof(openfilestruct));
Chris Allegretta's avatar
Chris Allegretta committed
651
652
653
654
655
656
657
658
659
660
661
662
663

    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,
664
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
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
{
    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
719
 * FALSE, a new entry is created; otherwise, the current entry is
 * updated.
720
 */
721
void add_open_file(int update)
722
{
723
    openfilestruct *tmp;
724

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

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

772
773
774
775
    /* start with default modification status: unmodified (and marking
       status, if available: unmarked) */
    open_files->file_flags = 0;

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

790
791
792
    /* 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
793
794
795
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
796
    }
797
798

#ifdef DEBUG
799
    fprintf(stderr, "filename is %s\n", open_files->filename);
800
801
802
803
804
#endif
}

/*
 * Read the current entry in the open_files structure and set up the
805
 * currently open file using that entry's information.
806
 */
807
void load_open_file(void)
808
{
809
    if (open_files == NULL)
810
	return;
811
812
813

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

Chris Allegretta's avatar
Chris Allegretta committed
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
    /* 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
839

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

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

Chris Allegretta's avatar
Chris Allegretta committed
849
    /* update the titlebar */
850
851
852
853
854
855
    clearok(topwin, FALSE);
    titlebar(NULL);
}

/*
 * Open the previous entry in the open_files structure.  If closing_file
856
857
858
 * is FALSE, update the current entry before switching from it.
 * Otherwise, we are about to close that entry, so don't bother doing
 * so.
859
 */
860
void open_prevfile(int closing_file)
861
{
862
    if (open_files == NULL)
863
	return;
864
865

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

870
    if (open_files->prev == NULL && open_files->next == NULL) {
871
872
873

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
874
	    statusbar(_("No more open file buffers"));
875
	return;
876
877
    }

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

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

    }

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

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

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

    }

    load_open_file();

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

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

910
void open_prevfile_void(void)
911
{
912
    open_prevfile(FALSE);
913
914
}

915
916
/*
 * Open the next entry in the open_files structure.  If closing_file is
917
918
 * FALSE, update the current entry before switching from it.  Otherwise,
 * we are about to close that entry, so don't bother doing so.
919
 */
920
void open_nextfile(int closing_file)
921
{
922
    if (open_files == NULL)
923
	return;
924
925

    /* if we're not about to close the current entry, update it before
926
       doing anything */
927
    if (!closing_file)
928
	add_open_file(TRUE);
929

930
    if (open_files->prev == NULL && open_files->next == NULL) {
931
932
933

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
934
	    statusbar(_("No more open file buffers"));
935
	return;
936
937
    }

938
    if (open_files->next != NULL) {
939
940
941
	open_files = open_files->next;

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

    }
946
    else if (open_files->prev != NULL) {
947
948

	/* if we're at the end, wrap around to the beginning */
949
	while (open_files->prev != NULL) {
950
951
952
	    open_files = open_files->prev;

#ifdef DEBUG
953
	    fprintf(stderr, "filename is %s\n", open_files->filename);
954
955
956
957
958
959
960
#endif

	}
    }

    load_open_file();

961
    statusbar(_("Switched to %s"),
962
963
      ((open_files->filename[0] == '\0') ? "New Buffer" :
	open_files->filename));
964

965
966
967
968
969
#ifdef DEBUG
    dump_buffer(current);
#endif
}

970
void open_nextfile_void(void)
971
{
972
    open_nextfile(FALSE);
973
974
}

975
976
/*
 * Delete an entry from the open_files filestruct.  After deletion of an
977
 * entry, the next or previous entry is opened, whichever is found first.
978
979
980
981
 * Return 0 on success or 1 on error.
 */
int close_open_file(void)
{
982
    openfilestruct *tmp;
983

984
    if (open_files == NULL)
985
986
	return 1;

Chris Allegretta's avatar
Chris Allegretta committed
987
988
989
    /* 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 */
990
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
991
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
992

993
    tmp = open_files;
994
995
996
997
998
999
    if (open_files->next != NULL)
	open_nextfile(TRUE);
    else if (open_files->prev != NULL)
	open_prevfile(TRUE);
    else
	return 1;
1000

1001
1002
    unlink_opennode(tmp);
    delete_opennode(tmp);
1003

1004
    shortcut_init(FALSE);
1005
1006
1007
    display_main_list();
    return 0;
}
1008
#endif /* MULTIBUFFER */
1009

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

1026
    /* first, get the current directory, and tack a slash onto the end of
1027
       it, unless it turns out to be "/", in which case leave it alone */
1028
1029
1030

    d_here = getcwd(NULL, PATH_MAX + 1);

1031
    if (d_here != NULL) {
1032
1033

	align(&d_here);
1034
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1035
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1036
1037
	    strcat(d_here, "/");
	}
1038
1039
1040
1041
1042

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

1045
	expanded_origpath = real_dir_from_tilde(origpath);
1046
	/* save the value of origpath in both d_there and d_there_file */
1047
1048
1049
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1050

1051
1052
1053
1054
1055
1056
	/* if we have a path but no filename, tack slashes onto the ends
	   of both d_there and d_there_file, if they don't end in slashes
	   already */
	if (path_only) {
	    tmp = d_there[strlen(d_there) - 1];
	    if (tmp != '/') {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1057
		d_there = charealloc(d_there, strlen(d_there) + 2);
1058
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1059
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1060
1061
1062
1063
		strcat(d_there_file, "/");
	    }
	}

1064
1065
1066
1067
1068
	/* 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 */
1069
	if (last_slash == NULL)
1070
	    d_there = mallocstrcpy(d_there, d_here);
1071
	else {
1072
1073
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1074
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1075
	    null_at(&d_there, last_slash_index + 1);
1076
1077
1078

	    /* and remove all non-file elements from d_there_file (i. e.
	       everything before and including the last slash); if we
1079
	       have a path but no filename, don't do anything */
1080
1081
1082
1083
1084
1085
	    if (!path_only) {
		last_slash = strrchr(d_there_file, '/');
		last_slash++;
		strcpy(d_there_file, last_slash);
		align(&d_there_file);
	    }
1086
1087
1088

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1089
1090
1091
		/* 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 */
1092
1093
1094
1095
1096
1097

		free(d_there);

		d_there = getcwd(NULL, PATH_MAX + 1);

		align(&d_there);
1098
		if (d_there != NULL) {
1099
1100
1101

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
1102
		    if (strcmp(d_there, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1103
			d_there = charealloc(d_there, strlen(d_there) + 2);
1104
1105
			strcat(d_there, "/");
		    }
1106
1107
1108
		}
		else
		    return NULL;
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
	    }

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

1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
	/* 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);
	}
1130
1131
1132
1133
1134
1135
1136
1137
1138

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

    return newpath;
}
1139
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1140
1141
1142

#ifndef DISABLE_SPELLER
/*
1143
1144
1145
 * 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.
1146
 */
1147
char *check_writable_directory(const char *path)
1148
{
1149
    char *full_path = get_full_path(path);
1150
    int writable;
1151
1152
    struct stat fileinfo;

1153
    /* if get_full_path() failed, return NULL */
1154
    if (full_path == NULL)
1155
	return NULL;
1156
1157
1158

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

    /* if the full path doesn't end in a slash (meaning get_full_path()
1162
1163
1164
1165
       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);
1166
	return NULL;
1167
    }
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177

    /* 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
1178
1179
1180
1181
1182
1183
1184
 * 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.
1185
 */
1186
1187
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1188
1189
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1190
    int filedesc;
1191

1192
1193
      /* 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,
1194
         leave full_tempdir set to NULL */
1195
    TMPDIR_env = getenv("TMPDIR");
1196
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1197
	full_tempdir = check_writable_directory(TMPDIR_env);
1198

1199
1200
1201
    /* 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 */
1202
    if (full_tempdir == NULL && dirname != NULL)
1203
	full_tempdir = check_writable_directory(dirname);
1204
1205
1206

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1207
    if (full_tempdir == NULL)
1208
	full_tempdir = check_writable_directory(P_tmpdir);
1209
1210

    /* if P_tmpdir didn't work, use /tmp instead */
1211
    if (full_tempdir == NULL) {
1212
1213
1214
1215
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1216
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1217
1218

    /* like tempnam(), use only the first 5 characters of the prefix */
1219
1220
1221
1222
1223
1224
1225
1226
    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) {
1227
	close(filedesc);
1228
1229
	unlink(full_tempdir);
	return full_tempdir;
1230
    }
1231
1232
1233

    free(full_tempdir);
    return NULL;
1234
1235
}
#endif /* !DISABLE_SPELLER */
1236
1237

#ifndef DISABLE_OPERATINGDIR
1238
1239
1240
1241
1242
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1243
    if (operating_dir == NULL)
1244
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1245

1246
1247
1248
    full_operating_dir = get_full_path(operating_dir);

    /* If get_full_path() failed or the operating directory is
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1249
     * inaccessible, unset operating_dir. */
1250
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1251
1252
1253
1254
1255
1256
1257
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1258
/* Check to see if we're inside the operating directory.  Return 0 if we
1259
1260
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1261
 * completion will work. */
1262
int check_operating_dir(const char *currpath, int allow_tabcomp)
1263
{
1264
1265
1266
1267
    /* The char *full_operating_dir is global for mem cleanup.  It
     * should have already been initialized by init_operating_dir().
     * Also, a relative operating directory path will only be handled
     * properly if this is done. */
1268

1269
1270
1271
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1272

1273
    /* If no operating directory is set, don't bother doing anything. */
1274
    if (operating_dir == NULL)
1275
	return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1276

1277
    assert(full_operating_dir != NULL);
1278
1279

    fullpath = get_full_path(currpath);
1280
1281
1282
1283
1284
1285
1286
1287

    /* fullpath == NULL means some directory in the path doesn't exist
     * or is unreadable.  If allow_tabcomp is zero, then currpath is
     * what the user typed somewhere.  We don't want to report a
     * non-existent directory as being outside the operating directory,
     * so we return 0.  If allow_tabcomp is nonzero, then currpath
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1288
    if (fullpath == NULL)
1289
	return allow_tabcomp;
1290
1291
1292
1293
1294

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

1295
1296
1297
1298
1299
    /* If both searches failed, we're outside the operating directory.
     * Otherwise, check the search results; if the full operating
     * directory path is not at the beginning of the full current path
     * (for normal usage) and vice versa (for tab completion, if we're
     * allowing it), we're outside the operating directory. */
1300
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1301
1302
	retval = 1;
    free(fullpath);	
1303
1304

    /* Otherwise, we're still inside it. */
1305
    return retval;
1306
}
1307
1308
#endif

1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
#ifndef NANO_SMALL
void init_backup_dir(void)
{
    char *full_backup_dir;

    if (backup_dir == NULL)
	return;

    full_backup_dir = get_full_path(backup_dir);

    /* If get_full_path() failed or the backup directory is
     * inaccessible, unset backup_dir. */
    if (full_backup_dir == NULL ||
	full_backup_dir[strlen(full_backup_dir) - 1] != '/') {
	free(full_backup_dir);
	free(backup_dir);
	backup_dir = NULL;
    } else {
	free(backup_dir);
	backup_dir = full_backup_dir;
    }
}
#endif

1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
/* Read from inn, write to out.  We assume inn is opened for reading,
 * and out for writing.  We return 0 on success, -1 on read error, -2 on
 * write error. */
int copy_file(FILE *inn, FILE *out)
{
    char buf[BUFSIZ];
    size_t charsread;
    int retval = 0;

    assert(inn != NULL && out != NULL);
    do {
	charsread = fread(buf, sizeof(char), BUFSIZ, inn);
	if (charsread == 0 && ferror(inn)) {
	    retval = -1;
	    break;
	}
	if (fwrite(buf, sizeof(char), charsread, out) < charsread) {
	    retval = -2;
	    break;
	}
    } while (charsread > 0);
    if (fclose(inn) == EOF)
	retval = -1;
    if (fclose(out) == EOF)
	retval = -2;
    return retval;
}

1361
/* Write a file out.  If tmp is FALSE, we set the umask to disallow
1362
1363
1364
 * anyone else from accessing the file, we don't set the global variable
 * filename to its name, and we don't print out how many lines we wrote
 * on the statusbar.
Chris Allegretta's avatar
Chris Allegretta committed
1365
 *
1366
1367
 * tmp means we are writing a temporary file in a secure fashion.  We
 * use it when spell checking or dumping the file on an error.
1368
 *
1369
1370
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1371
 *
1372
1373
 * nonamechange means don't change the current filename.  It is ignored
 * if tmp is FALSE or if we're appending/prepending.
1374
1375
 *
 * Return -1 on error, 1 on success. */
1376
int write_file(const char *name, int tmp, int append, int nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1377
{
1378
1379
    int retval = -1;
	/* Instead of returning in this function, you should always
1380
1381
1382
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
    const filestruct *fileptr = fileage;
1383
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1384
	/* The file descriptor we use. */
1385
    mode_t original_umask = 0;
1386
1387
	/* Our umask, from when nano started. */
    int realexists;
1388
	/* The result of stat().  TRUE if the file exists, FALSE
1389
	 * otherwise.  If name is a link that points nowhere, realexists
1390
	 * is FALSE. */
1391
1392
1393
    struct stat st;
	/* The status fields filled in by stat(). */
    int anyexists;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1394
	/* The result of lstat().  Same as realexists unless name is a
1395
1396
1397
1398
	 * link. */
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1399
	/* name after tilde expansion. */
1400
1401
1402
1403
    FILE *f;
	/* The actual file, realname, we are writing to. */
    char *tempname = NULL;
	/* The temp file name we write to on prepend. */
Chris Allegretta's avatar
Chris Allegretta committed
1404

1405
    assert(name != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1406
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1407
1408
1409
	statusbar(_("Cancelled"));
	return -1;
    }
1410
1411
    if (!tmp)
	titlebar(NULL);
1412

1413
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1414

1415
#ifndef DISABLE_OPERATINGDIR
1416
    /* If we're writing a temporary file, we're probably going outside
1417
     * the operating directory, so skip the operating directory test. */
1418
    if (!tmp && check_operating_dir(realname, FALSE) != 0) {
1419
1420
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1421
1422
1423
    }
#endif

1424
1425
1426
1427
1428
1429
1430
    anyexists = lstat(realname, &lst) != -1;
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
    /* If NOFOLLOW_SYMLINKS is set, it doesn't make sense to prepend or
     * append to a symlink.  Here we warn about the contradiction. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode)) {
1431
1432
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1433
1434
1435
	goto cleanup_and_exit;
    }

1436
    /* Save the state of file at the end of the symlink (if there is
1437
1438
     * one). */
    realexists = stat(realname, &st) != -1;
1439

1440
1441
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1442
1443
1444
1445
1446
1447
1448
1449
     * temporary, and the file already exists.  Furthermore, if we
     * aren't appending, prepending, or writing a selection, we backup
     * only if the file has not been modified by someone else since nano
     * opened it. */
    if (ISSET(BACKUP_FILE) && !tmp && realexists != 0 &&
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1450
	FILE *backup_file;
1451
	char *backupname;
1452
	struct utimbuf filetime;
1453
	int copy_status;
1454

1455
	/* Save the original file's access and modification times. */
1456
1457
1458
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1459
	/* Open the original file to copy to the backup. */
1460
	f = fopen(realname, "rb");
1461
	if (f == NULL) {
1462
	    statusbar(_("Error reading %s: %s"), realname,
1463
		strerror(errno));
1464
	    goto cleanup_and_exit;
1465
1466
	}

1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
	/* If backup_dir is set, we set backupname to
	 * backup_dir/backupname~, where backupnae is the canonicalized
	 * absolute pathname of realname with every '/' replaced with a
	 * '!'.  This means that /home/foo/file is backed up in
	 * backup_dir/!home!foo!file~. */
	if (backup_dir != NULL) {
	    char *canon_realname = get_full_path(realname);
	    size_t i;

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

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

1500
1501
1502
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1503
	backup_file = fopen(backupname, "wb");
1504
1505
1506
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
	    statusbar(_("Error writing %s: %s"), backupname, strerror(errno));
1507
	    free(backupname);
1508
1509
1510
1511
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1512
1513
1514
	}

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

1518
1519
1520
1521
1522
1523
1524
1525
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
	/* And set metadata. */
	if (copy_status != 0 || chown(backupname, originalfilestat.st_uid,
		originalfilestat.st_gid) == -1 ||
		utime(backupname, &filetime) == -1) {
	    free(backupname);
	    if (copy_status == -1)
1526
		statusbar(_("Error reading %s: %s"), realname, strerror(errno));
1527
1528
1529
1530
1531
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1532
1533
	free(backupname);
    }
1534
#endif /* !NANO_SMALL */
1535

1536
1537
1538
1539
1540
1541
    /* If NOFOLLOW_SYMLINKS and the file is a link, we aren't doing
     * prepend or append.  So we delete the link first, and just
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1542
	goto cleanup_and_exit;
1543
    }
1544

1545
1546
    original_umask = umask(0);
    umask(original_umask);
1547

1548
    /* If we create a temp file, we don't let anyone else access it.  We
1549
     * create a temp file if tmp is TRUE or if we're prepending. */
1550
1551
1552
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

1553
    /* If we're prepending, copy the file to a temp file. */
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
    if (append == 2) {
	int fd_source;
	FILE *f_source = NULL;

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

1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
	fd_source = open(realname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
	}
	if (f_source == NULL) {
	    statusbar(_("Error reading %s: %s"), realname, strerror(errno));
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

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

1594
1595
    /* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This is
     * now copied from joe, because wiggy says so *shrug*. */
1596
1597
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
1598
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1599

1600
    /* Set the umask back to the user's original value. */
1601
1602
1603
1604
1605
1606
1607
1608
    umask(original_umask);

    /* First, just give up if we couldn't even open the file. */
    if (fd == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	unlink(tempname);
	goto cleanup_and_exit;
    }
1609

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1610
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1611
    if (f == NULL) {
1612
1613
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1614
	goto cleanup_and_exit;
1615
1616
    }

1617
    /* There might not be a magicline.  There won't be when writing out
1618
1619
1620
1621
1622
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1623

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

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

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

1632
	if (size < data_len) {
1633
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1634
	    fclose(f);
1635
	    goto cleanup_and_exit;
1636
	}
1637
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1638
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1639
1640
1641
1642
1643
	    if (putc('\r', f) == EOF) {
		statusbar(_("Error writing %s: %s"), realname, strerror(errno));
		fclose(f);
		goto cleanup_and_exit;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1644
1645

	if (!ISSET(MAC_FILE))
1646
#endif
1647
1648
1649
1650
1651
	    if (putc('\n', f) == EOF) {
		statusbar(_("Error writing %s: %s"), realname, strerror(errno));
		fclose(f);
		goto cleanup_and_exit;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1652
1653
1654
1655
1656

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

1657
    /* If we're prepending, open the temp file, and append it to f. */
1658
    if (append == 2) {
1659
1660
1661
1662
1663
1664
1665
1666
	int fd_source;
	FILE *f_source = NULL;

	fd_source = open(tempname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1667
	}
1668
	if (f_source == NULL) {
1669
1670
	    statusbar(_("Error reading %s: %s"), tempname, strerror(errno));
	    fclose(f);
1671
	    goto cleanup_and_exit;
1672
1673
	}

1674
1675
1676
	if (copy_file(f_source, f) == -1
		|| unlink(tempname) == -1) {
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1677
	    goto cleanup_and_exit;
1678
	}
1679
1680
1681
1682
    } else if (fclose(f) == EOF) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	unlink(tempname);
	goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1683
    }
1684

1685
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1686
	if (!nonamechange) {
1687
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1688
1689
#ifdef ENABLE_COLOR
	    update_color();
1690
1691
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1692
1693
#endif
	}
1694

1695
1696
1697
1698
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1699
1700
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1701
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1702
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1703
    }
1704
1705
1706
1707
1708

    retval = 1;

  cleanup_and_exit:
    free(realname);
1709
    free(tempname);
1710
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1711
1712
}

1713
1714
1715
#ifndef NANO_SMALL
/* Write a marked selection from a file out.  First, set fileage and
 * filebot as the top and bottom of the mark, respectively.  Then call
1716
1717
1718
1719
1720
 * write_file() with the values of name, temp, and append, and with
 * nonamechange set to TRUE so that we don't change the current
 * filename.  Finally, set fileage and filebot back to their old values
 * and return. */
int write_marked(const char *name, int tmp, int append)
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
{
    int retval = -1;
    filestruct *fileagebak = fileage;
    filestruct *filebotbak = filebot;
    int oldmod = ISSET(MODIFIED);
	/* write_file() unsets the MODIFIED flag. */
    size_t topx;
	/* The column of the beginning of the mark. */
    char origchar;
	/* We replace the character at the end of the mark with '\0'.
	 * We save the original character, to restore it. */
    char *origcharloc;
	/* The location of the character we nulled. */

    if (!ISSET(MARK_ISSET))
	return -1;

    /* Set fileage as the top of the mark, and filebot as the bottom. */
    if (current->lineno > mark_beginbuf->lineno ||
		(current->lineno == mark_beginbuf->lineno &&
		current_x > mark_beginx)) {
	fileage = mark_beginbuf;
	topx = mark_beginx;
	filebot = current;
	origcharloc = current->data + current_x;
    } else {
	fileage = current;
	topx = current_x;
	filebot = mark_beginbuf;
	origcharloc = mark_beginbuf->data + mark_beginx;
    }
    origchar = *origcharloc;
    *origcharloc = '\0';
    fileage->data += topx;

    /* If the line at filebot is blank, treat it as the magicline and
     * hence the end of the file.  Otherwise, treat the line after
     * filebot as the end of the file. */
    if (filebot->data[0] != '\0' && filebot->next != NULL)
	filebot = filebot->next;

1762
    retval = write_file(name, tmp, append, TRUE);
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775

    /* Now restore everything. */
    fileage->data -= topx;
    *origcharloc = origchar;
    fileage = fileagebak;
    filebot = filebotbak;
    if (oldmod)
	set_modified();

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

1776
int do_writeout(int exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1777
{
1778
1779
    int i;
    int append = 0;
1780
#ifdef NANO_EXTRA
1781
    static int did_cred = FALSE;
1782
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1783

1784
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1785
    currshortcut = writefile_list;
1786
1787
#endif

1788
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1789
1790
1791
1792
1793
	i = write_file(filename, FALSE, 0, FALSE);
	if (i == 1) {
	    /* Write succeeded. */
	    display_main_list();
	    return 1;
1794
	}
Chris Allegretta's avatar
Chris Allegretta committed
1795
1796
    }

1797
#ifndef NANO_SMALL
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
    if (ISSET(MARK_ISSET) && !exiting)
	answer = mallocstrcpy(answer, "");
    else
#endif
	answer = mallocstrcpy(answer, filename);

    while (TRUE) {
	const char *msg;
#ifndef NANO_SMALL
	char *ans = mallocstrcpy(NULL, answer);
1808
1809
	const char *formatstr, *backupstr;

1810
	if (ISSET(MAC_FILE))
1811
	   formatstr = N_(" [Mac Format]");
1812
	else if (ISSET(DOS_FILE))
1813
	   formatstr = N_(" [DOS Format]");
1814
1815
1816
	else
	   formatstr = "";

1817
	if (ISSET(BACKUP_FILE))
1818
	   backupstr = N_(" [Backup]");
1819
1820
1821
	else
	   backupstr = "";

1822
	/* Be nice to the translation folks. */
1823
	if (ISSET(MARK_ISSET) && !exiting) {
1824
	    if (append == 2)
1825
		msg = N_("Prepend Selection to File");
Chris Allegretta's avatar
Chris Allegretta committed
1826
	    else if (append == 1)
1827
		msg = N_("Append Selection to File");
1828
	    else
1829
		msg = N_("Write Selection to File");
1830
1831
	} else
#endif /* !NANO_SMALL */
1832
	if (append == 2)
1833
	    msg = N_("File Name to Prepend to");
Chris Allegretta's avatar
Chris Allegretta committed
1834
	else if (append == 1)
1835
	    msg = N_("File Name to Append to");
1836
	else
1837
	    msg = N_("File Name to Write");
1838

1839
1840
1841
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
1842
1843
	i = statusq(!ISSET(RESTRICTED) || filename[0] == '\0',
		writefile_list,
1844
#ifndef NANO_SMALL
1845
		ans, NULL, "%s%s%s", _(msg), formatstr, backupstr
1846
#else
1847
		filename, "%s", _(msg)
1848
1849
1850
1851
1852
1853
#endif
		);

#ifndef NANO_SMALL
	free(ans);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1854

1855
1856
1857
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
1858
	    return -1;
1859
	}
Chris Allegretta's avatar
Chris Allegretta committed
1860

1861
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1862
	if (i == NANO_TOFILES_KEY) {
1863
	    char *tmp = do_browse_from(answer);
1864

1865
	    currshortcut = writefile_list;
1866
1867
1868
1869
	    if (tmp == NULL)
		continue;
	    free(answer);
	    answer = tmp;
1870
	} else
1871
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1872
#ifndef NANO_SMALL
1873
1874
1875
	if (i == TOGGLE_DOS_KEY) {
	    UNSET(MAC_FILE);
	    TOGGLE(DOS_FILE);
1876
	    continue;
1877
1878
1879
	} else if (i == TOGGLE_MAC_KEY) {
	    UNSET(DOS_FILE);
	    TOGGLE(MAC_FILE);
1880
	    continue;
1881
1882
	} else if (i == TOGGLE_BACKUP_KEY) {
	    TOGGLE(BACKUP_FILE);
1883
1884
	    continue;
	} else
1885
#endif /* !NANO_SMALL */
1886
1887
1888
1889
1890
1891
1892
	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
1893

Chris Allegretta's avatar
Chris Allegretta committed
1894
#ifdef DEBUG
1895
	fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1896
#endif
1897
1898

#ifdef NANO_EXTRA
1899
	if (exiting && !ISSET(TEMP_FILE) && strcasecmp(answer, "zzy") == 0
1900
		&& !did_cred) {
1901
	    do_credits();
1902
	    did_cred = TRUE;
1903
1904
	    return -1;
	}
1905
#endif
1906
	if (append == 0 && strcmp(answer, filename) != 0) {
1907
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1908

1909
	    if (!stat(answer, &st)) {
1910
		i = do_yesno(FALSE, _("File exists, OVERWRITE ? "));
1911
1912
		if (i == 0 || i == -1)
		    continue;
1913
1914
1915
1916
1917
	    /* If we're using restricted mode, we aren't allowed to
	     * change the name of a file once it has one because that
	     * would allow reading from or writing to files not
	     * specified on the command line.  In this case, don't
	     * bother showing the "Different Name" prompt. */
1918
	    } else if (!ISSET(RESTRICTED) && filename[0] != '\0'
1919
#ifndef NANO_SMALL
1920
		&& (exiting || !ISSET(MARK_ISSET))
1921
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1922
		) {
1923
		i = do_yesno(FALSE, _("Save file under DIFFERENT NAME ? "));
1924
1925
1926
		if (i == 0 || i == -1)
		    continue;
	    }
1927
	}
Chris Allegretta's avatar
Chris Allegretta committed
1928

1929
#ifndef NANO_SMALL
1930
1931
1932
1933
	/* Here's where we allow the selected text to be written to a
	 * separate file.  If we're using restricted mode, this is
	 * disabled since it allows reading from or writing to files not
	 * specified on the command line. */
1934
	if (!ISSET(RESTRICTED) && !exiting && ISSET(MARK_ISSET))
1935
	    i = write_marked(answer, FALSE, append);
1936
	else
1937
#endif /* !NANO_SMALL */
1938
	    i = write_file(answer, FALSE, append, FALSE);
1939

1940
#ifdef ENABLE_MULTIBUFFER
1941
	/* If we're not about to exit, update the current entry in
1942
	 * the open_files structure. */
1943
	if (!exiting)
1944
	    add_open_file(TRUE);
1945
#endif
1946
1947
	display_main_list();
	return i;
1948
    } /* while (TRUE) */
Chris Allegretta's avatar
Chris Allegretta committed
1949
1950
}

1951
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1952
{
1953
    do_writeout(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
1954
}
Chris Allegretta's avatar
Chris Allegretta committed
1955

1956
/* Return a malloc()ed string containing the actual directory, used
1957
1958
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1959
{
Chris Allegretta's avatar
Chris Allegretta committed
1960
    char *dirtmp = NULL;
1961

1962
    if (buf[0] == '~') {
1963
1964
1965
1966
1967
1968
1969
	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++)
	    ;

1970
1971
	/* Determine home directory using getpwuid() or getpwent(),
	   don't rely on $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
1972
1973
1974
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
1975
1976
1977
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
1978
			strncmp(userdata->pw_name, buf + 1, i - 1) != 0);
Chris Allegretta's avatar
Chris Allegretta committed
1979
1980
	}
	endpwent();
1981

1982
	if (userdata != NULL) {	/* User found */
1983
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1984
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1985
	}
1986
    }
1987

1988
1989
1990
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1991
    return dirtmp;
1992
1993
}

Chris Allegretta's avatar
Chris Allegretta committed
1994
#ifndef DISABLE_TABCOMP
1995
1996
1997
/* 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. */
1998
int append_slash_if_dir(char *buf, bool *lastwastab, int *place)
1999
{
2000
    char *dirptr = real_dir_from_tilde(buf);
2001
    struct stat fileinfo;
2002
    int ret = 0;
2003

2004
    assert(dirptr != buf);
2005

2006
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
2007
	strncat(buf, "/", 1);
2008
	(*place)++;
2009
	/* now we start over again with # of tabs so far */
2010
	*lastwastab = FALSE;
2011
	ret = 1;
2012
2013
    }

2014
    free(dirptr);
2015
    return ret;
2016
}
Chris Allegretta's avatar
Chris Allegretta committed
2017
2018

/*
Chris Allegretta's avatar
Chris Allegretta committed
2019
2020
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
2021
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
 *
 * 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)
{
2039
    char **matches = (char **)NULL;
2040
    char *matchline = NULL;
2041
    struct passwd *userdata;
2042

2043
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2044
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2045

2046
    strcat(buf, "*");
2047

2048
    while ((userdata = getpwent()) != NULL) {
2049

2050
	if (check_wildcard_match(userdata->pw_name, &buf[1])) {
2051
2052
2053
2054

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

2056
2057
2058
2059
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2060
	    if (operating_dir != NULL) {
2061
		if (check_operating_dir(userdata->pw_dir, TRUE) != 0)
2062
2063
2064
2065
		    continue;
	    }
#endif

2066
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2067
2068
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
2069
	    ++(*num_matches);
2070

2071
2072
2073
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2074
	}
2075
2076
    }
    endpwent();
2077

2078
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2079
2080
2081
2082
2083
2084
2085
}

/* 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
2086
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2087
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2088
2089
2090
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2091
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2092
2093
2094
2095

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

2096
    /* Okie, if there's a / in the buffer, strip out the directory part */
2097
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2098
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2099
2100
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2101
	    tmp--;
2102

Chris Allegretta's avatar
Chris Allegretta committed
2103
2104
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2105
2106
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2107

Chris Allegretta's avatar
Chris Allegretta committed
2108
    } else {
2109

Chris Allegretta's avatar
Chris Allegretta committed
2110
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2111
2112
2113
2114
2115
2116
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2117
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2118
2119
2120
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2121

Chris Allegretta's avatar
Chris Allegretta committed
2122
2123
2124
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2125
2126

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2127
    fprintf(stderr, "\nDir = %s\n", dirname);
2128
2129
2130
2131
2132
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2133
    dir = opendir(dirname);
2134
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2135
2136
2137
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2138
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2139
2140
2141
2142
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2143
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2144
2145
#endif
	/* See if this matches */
2146
	if (check_wildcard_match(next->d_name, tmp)) {
2147
2148
2149
2150

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2151
2152
2153
2154
2155
2156
2157
2158

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

2159
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2160
2161
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2162
2163
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2164
		if (check_operating_dir(tmp2, TRUE) != 0) {
2165
2166
2167
2168
2169
2170
2171
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2172
	    tmp2 = NULL;
2173
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2174
2175
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2176
	    ++*num_matches;
2177
2178
2179
2180

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2181
2182
	}
    }
2183
2184
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2185

2186
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2187
2188
}

Chris Allegretta's avatar
Chris Allegretta committed
2189
2190
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
2191
2192
char *input_tab(char *buf, int place, bool *lastwastab, int *newplace,
	bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2193
2194
{
    /* Do TAB completion */
2195
    static int num_matches = 0, match_matches = 0;
2196
    static char **matches = (char **)NULL;
2197
    int pos = place, i = 0, col = 0, editline = 0;
2198
    int longestname = 0, is_dir = 0;
2199
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2200

2201
    *list = FALSE;
2202

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2203
    if (*lastwastab == FALSE) {
2204
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2205

2206
	*lastwastab = TRUE;
2207

Chris Allegretta's avatar
Chris Allegretta committed
2208
2209
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2210
2211
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2212

2213
2214
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2215
2216

	/* skip any leading white space */
2217
	while (*tmp && isblank(*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2218
2219
2220
2221
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2222
2223
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2224
	    free(matches);
2225
	    matches = (char **)NULL;
2226
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2227
2228
2229
2230
2231
	}

	/* 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
2232
2233
2234
2235
2236
	/* 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. */
2237
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2238
	    buf = mallocstrcpy(buf, tmp);
2239
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2240
	}
2241
2242
2243
2244
2245
2246
	/* 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
2247
2248
2249

	/* Try to match everything in the current working directory that
	 * matches.  */
2250
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2251
2252
2253
	    matches = cwd_tab_completion(tmp, &num_matches);

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

2256
2257
2258
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2259
	/* Did we find exactly one match? */
2260
	switch (num_matches) {
2261
	case 0:
2262
	    blank_edit();
2263
	    wrefresh(edit);
2264
2265
	    break;
	case 1:
2266

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

2269
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2270
2271
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2272
		tmp++;
2273
	    } else
2274
2275
		tmp = buf;

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

2279
	    if (is_dir != 0)
2280
		break;
2281
2282

	    copyto = tmp;
2283
2284
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2285
2286
		tmp++;

2287
	    /* write out the matched name */
2288
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2289
2290
	    *newplace += strlen(matches[0]) - pos;

2291
2292
2293
2294
2295
2296
2297
	    /* 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;

2298
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2299
	    append_slash_if_dir(buf, lastwastab, newplace);
2300

2301
2302
	    break;
	default:
2303
	    /* Check to see if all matches share a beginning, and, if so,
2304
	       tack it onto buf and then beep */
2305

2306
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2307
2308
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2309
		tmp++;
2310
	    } else
2311
2312
		tmp = buf;

2313
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2314
		 pos <= strlen(matches[0]); pos++)
2315
2316
		tmp++;

2317
	    while (TRUE) {
2318
2319
2320
2321
2322
2323
2324
2325
		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++;
		}
2326
2327
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2328
		    /* All the matches have the same character at pos+1,
2329
		       so paste it into buf... */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2330
		    buf = charealloc(buf, strlen(buf) + 2);
2331
		    strncat(buf, matches[0] + pos, 1);
2332
		    *newplace += 1;
2333
		    pos++;
2334
		} else {
2335
2336
2337
2338
2339
		    beep();
		    break;
		}
	    }
	}
Chris Allegretta's avatar
Chris Allegretta committed
2340
2341
2342
2343
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2344
	if (matches != NULL && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2345
2346
2347
2348
2349

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

2350
	    editline = 0;
2351

2352
2353
2354
2355
2356
2357
2358
2359
	    /* 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;

2360
	    foo = charalloc(longestname + 5);
2361

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

2365
		/* make each filename shown be the same length as the longest
2366
		   filename, with two spaces at the end */
2367
2368
2369
2370
2371
2372
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2373
2374
		/* Disable el cursor */
		curs_set(0);
2375
2376
2377
2378
2379
2380
		/* 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 */
2381
		if (col > COLS - longestname && i + 1 < num_matches) {
2382
2383
		    editline++;
		    wmove(edit, editline, 0);
2384
2385
2386
2387
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2388
2389
2390
		    col = 0;
		}
	    }
2391
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2392
	    wrefresh(edit);
2393
	    *list = TRUE;
2394
2395
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2396
2397
    }

2398
2399
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
2400
    if (*list == FALSE)
2401
	edit_refresh();
2402
2403
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2404
}
2405
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2406

2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
/* Only print the last part of a path; isn't there a shell
 * command for this? */
const char *tail(const char *foo)
{
    const char *tmp = foo + strlen(foo);

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

    if (*tmp == '/')
	tmp++;

    return tmp;
}

2422
#ifndef DISABLE_BROWSER
2423
/* Our sort routine for file listings -- sort directories before
2424
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2425
2426
int diralphasort(const void *va, const void *vb)
{
2427
2428
2429
2430
    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
2431

2432
    if (aisdir != 0 && bisdir == 0)
2433
	return -1;
2434
    if (aisdir == 0 && bisdir != 0)
2435
	return 1;
2436

2437
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2438
2439
}

2440
/* Free our malloc()ed memory */
2441
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2442
{
2443
2444
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2445
2446
2447
    free(array);
}

2448
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2449
2450
void striponedir(char *foo)
{
2451
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2452

2453
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2454
    /* Don't strip the root dir */
2455
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2456
2457
	return;

2458
2459
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2460
    if (*tmp == '/')
2461
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2462
2463
2464
2465
2466

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

    if (tmp != foo)
2467
2468
2469
2470
2471
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2472
    }
Chris Allegretta's avatar
Chris Allegretta committed
2473
2474
}

2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
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;
}

2485
/* Initialize the browser code, including the list of files in *path */
2486
char **browser_init(const char *path, int *longest, int *numents)
2487
2488
2489
{
    DIR *dir;
    struct dirent *next;
2490
    char **filelist;
2491
    int i = 0;
2492
    size_t path_len;
2493
2494

    dir = opendir(path);
2495
    if (dir == NULL)
2496
2497
2498
2499
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
2500
	if (strcmp(next->d_name, ".") == 0)
2501
2502
2503
2504
2505
2506
2507
2508
	   continue;
	(*numents)++;
	if (strlen(next->d_name) > *longest)
	    *longest = strlen(next->d_name);
    }
    rewinddir(dir);
    *longest += 10;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2509
    filelist = (char **)nmalloc(*numents * sizeof (char *));
2510

2511
    if (strcmp(path, "/") == 0)
2512
2513
2514
	path = "";
    path_len = strlen(path);

2515
    while ((next = readdir(dir)) != NULL) {
2516
	if (strcmp(next->d_name, ".") == 0)
2517
2518
	   continue;

2519
2520
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2521
2522
	i++;
    }
2523
    closedir(dir);
2524
2525
2526
2527
2528
2529
2530

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2531
/* Our browser function.  inpath is the path to start browsing from */
2532
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2533
2534
2535
2536
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2537
    int numents = 0, i = 0, j = 0, kbinput = ERR, meta_key, longest = 0;
2538
2539
    int abort = 0, col = 0, selected = 0, editline = 0, width = 0;
    int filecols = 0, lineno = 0;
2540
    char **filelist = (char **)NULL;
2541
#ifndef DISABLE_MOUSE
2542
2543
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2544

2545
2546
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2547
2548
2549
    /* 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 */
2550
    if (path != NULL && strcmp(path, inpath) != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2551
2552
2553
2554
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2555
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2556
    if (path == NULL)
2557
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2558
2559

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

2562
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2563
2564
2565
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2566
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2567
2568
2569
2570
2571
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2572
2573

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2574
    do {
2575
2576
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2577

2578
	check_statblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2579

2580
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2581
	currshortcut = browser_list;
2582
2583
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2584
2585
 	editline = 0;
	col = 0;
2586
	    
2587
	/* Compute line number we're on now, so we don't divide by zero later */
2588
2589
2590
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2591
2592

	switch (kbinput) {
2593

2594
#ifndef DISABLE_MOUSE
2595
	case KEY_MOUSE:
2596
	    if (getmouse(&mevent) == ERR)
2597
		return retval;
2598
2599
2600
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2601
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2602
2603
2604
2605
		int selectedbackup = selected;

		mevent.y -= 2;

2606
2607
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2608
		selected = (lineno / editwinrows) * editwinrows * width
2609
2610
2611
2612
2613
2614
			+ 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--;
2615
2616
2617

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2618
		if (selected > numents - 1)
2619
		    selected = numents - 1;
2620
		else if (selectedbackup == selected)
2621
2622
2623
2624
		    ungetch('s');	/* Unget the 'select' key */
	    } else	/* Must be clicking a shortcut */
		do_mouse();

2625
2626
            break;
#endif
2627
	case NANO_PREVLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2628
2629
2630
	    if (selected - width >= 0)
		selected -= width;
	    break;
2631
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2632
2633
2634
	    if (selected > 0)
		selected--;
	    break;
2635
	case NANO_NEXTLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2636
2637
2638
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
2639
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2640
2641
2642
2643
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2644
	case NANO_PREVPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2645
	case '-': /* Pico compatibility */
2646
	    if (selected >= (editwinrows + lineno % editwinrows) * width)
2647
		selected -= (editwinrows + lineno % editwinrows) * width;
Chris Allegretta's avatar
Chris Allegretta committed
2648
2649
2650
2651
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2652
	case NANO_NEXTPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2653
	case ' ': /* Pico compatibility */
2654
2655
	    selected += (editwinrows - lineno % editwinrows) * width;
	    if (selected >= numents)
Chris Allegretta's avatar
Chris Allegretta committed
2656
2657
		selected = numents - 1;
	    break;
2658
2659
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2660
	case '?': /* Pico compatibility */
2661
#ifndef DISABLE_HELP
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2662
	    do_help();
2663
	    curs_set(0);
2664
2665
2666
#else
	    nano_disabled_msg();
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2667
	    break;
2668
	case NANO_ENTER_KEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2669
2670
	case 'S': /* Pico compatibility */
	case 's':
Chris Allegretta's avatar
Chris Allegretta committed
2671
	    /* You can't cd up from / */
2672
2673
	    if (strcmp(filelist[selected], "/..") == 0 &&
		strcmp(path, "/") == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2674
		statusbar(_("Can't move up a directory"));
2675
		beep();
Rocco Corsi's avatar
   
Rocco Corsi committed
2676
2677
2678
		break;
	    }

2679
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2680
2681
2682
	    /* Note: the selected file can be outside the operating
	     * directory if it is .. or if it is a symlink to 
	     * directory outside the operating directory. */
2683
	    if (check_operating_dir(filelist[selected], FALSE) != 0) {
2684
2685
		statusbar(_("Can't go outside of %s in restricted mode"),
			operating_dir);
2686
2687
		beep();
		break;
2688
2689
2690
	    }
#endif

2691
	    if (stat(filelist[selected], &st) == -1) {
2692
2693
		statusbar(_("Can't open \"%s\": %s"), filelist[selected],
			strerror(errno));
2694
2695
2696
		beep();
		break;
	    }
2697

2698
2699
2700
2701
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2702
2703
	    }

2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
	    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
2714
		}
2715
2716
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2717

2718
2719
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
2720
2721
		statusbar(_("Can't open \"%s\": %s"), new_path,
			strerror(errno));
2722
2723
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2724
	    }
2725
2726
2727
2728
2729
2730
2731

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

2732
	/* Go to a specific directory */
Rocco Corsi's avatar
   
Rocco Corsi committed
2733
	case NANO_GOTO_KEY:
2734
	case NANO_GOTO_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2735
2736
	case 'G': /* Pico compatibility */
	case 'g':
Rocco Corsi's avatar
   
Rocco Corsi committed
2737
	    curs_set(1);
2738
	    j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2739
#ifndef NANO_SMALL
2740
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2741
#endif
2742
		_("Go To Directory"));
2743
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2744
2745
2746
	    curs_set(0);

	    if (j < 0) {
2747
		statusbar(_("Cancelled"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2748
2749
2750
		break;
	    }

2751
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2752

2753
2754
2755
	    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
2756
2757
	    }

2758
#ifndef DISABLE_OPERATINGDIR
2759
	    if (check_operating_dir(new_path, FALSE) != 0) {
2760
2761
2762
2763
2764
2765
2766
		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
2767
2768
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2769
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2770
		break;
2771
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2772
2773

	    /* Start over again with the new path value */
2774
2775
	    free_charptrarray(filelist, numents);
	    free(foo);
2776
2777
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2778
2779
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2780
	/* Stuff we want to abort the browser */
2781
	case NANO_CANCEL_KEY:
2782
	case NANO_EXIT_KEY:
2783
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2784
2785
	case 'E': /* Pico compatibility */
	case 'e':
2786
2787
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2788
2789
2790
2791
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2792
2793
	blank_edit();

2794
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
	    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 */
2809
2810
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2811
2812
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2813
2814
2815
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2816
2817
2818
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
2819
		    stat(filelist[j], &st);
2820
2821
2822
2823
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2824
2825
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2826
			(int) st.st_size);
2827
2828
2829
2830
2831
2832
		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);
2833
		else /* It's more than 1 k and less than a meg */
2834
2835
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2836
2837
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2838
	    /* Highlight the currently selected file/dir */
2839
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2840
		wattron(edit, A_REVERSE);
2841
2842
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2843
2844
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2845
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2846
2847
2848
2849
2850
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2851
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2852
2853
2854
2855
2856
2857
2858
2859
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
 	wrefresh(edit);
2860
    } while ((kbinput = get_kbinput(edit, &meta_key)) != NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
Chris Allegretta's avatar
Chris Allegretta committed
2861
2862
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2863
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2864
2865
    edit_refresh();

Chris Allegretta's avatar
Chris Allegretta committed
2866
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2867
2868
2869
2870
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2871

2872
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2873
 starts do_browser from there, else from the current dir */
2874
char *do_browse_from(const char *inpath)
2875
2876
{
    struct stat st;
2877
    char *bob;
2878
2879
2880
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2881

2882
2883
2884
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2885

2886
2887
2888
2889
2890
2891
2892
2893
    /*
     * 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);
2894
2895
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2896
	    path = getcwd(NULL, PATH_MAX + 1);
2897
	}
2898
    }
2899

2900
2901
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2902
    if (check_operating_dir(path, FALSE) != 0)
2903
2904
2905
2906
2907
2908
2909
2910
2911
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2912
    return bob;
2913
}
Chris Allegretta's avatar
Chris Allegretta committed
2914
#endif /* !DISABLE_BROWSER */
2915

2916
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2917
2918
2919
void load_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2920
    const struct passwd *userage = NULL;
2921
2922
    static char *nanohist;
    char *buf, *ptr;
Chris Allegretta's avatar
Chris Allegretta committed
2923
    char *homenv = getenv("HOME");
2924
2925
2926
    historyheadtype *history = &search_history;


Chris Allegretta's avatar
Chris Allegretta committed
2927
    if (homenv != NULL) {
2928
2929
	nanohist = charealloc(nanohist, strlen(homenv) + 15);
	sprintf(nanohist, "%s/.nano_history", homenv);
Chris Allegretta's avatar
Chris Allegretta committed
2930
2931
2932
    } else {
	userage = getpwuid(geteuid());
	endpwent();
2933
2934
	nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
	sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2935
2936
    }

2937
    /* assume do_rcfile() has reported missing home dir */
Chris Allegretta's avatar
Chris Allegretta committed
2938
2939

    if (homenv != NULL || userage != NULL) {
2940
	hist = fopen(nanohist, "r");
2941
	if (hist == NULL) {
2942
	    if (errno != ENOENT) {
2943
2944
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2945
		rcfile_error(N_("Error reading %s: %s"), nanohist, strerror(errno));
2946
	    }
2947
2948
2949
2950
2951
	    free(nanohist);
	} else {
	    buf = charalloc(1024);
	    while (fgets(buf, 1023, hist) != 0) {
		ptr = buf;
2952
		while (*ptr != '\n' && *ptr != '\0' && ptr < buf + 1023)
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
		    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
2972
    const struct passwd *userage = NULL;
2973
    char *nanohist = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2974
    char *homenv = getenv("HOME");
2975
2976
2977
    historytype *h;

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

Chris Allegretta's avatar
Chris Allegretta committed
2982
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2983
	nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
2984
2985
2986
2987
	sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2988
	nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
2989
	sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2990
2991
2992
    }

    if (homenv != NULL || userage != NULL) {
2993
	hist = fopen(nanohist, "wb");
2994
	if (hist == NULL)
2995
	    rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2996
	else {
2997
2998
2999
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
	    /* write oldest first */
3000
	    for (h = search_history.tail; h->prev; h = h->prev) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3001
		h->data = charealloc(h->data, strlen(h->data) + 2);
3002
3003
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
3004
		    rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
3005
3006
3007
3008
		    goto come_from;
		}
	    }
	    if (fputs("\n", hist) == EOF) {
3009
		    rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
3010
3011
		    goto come_from;
	    }
3012
	    for (h = replace_history.tail; h->prev; h = h->prev) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3013
		h->data = charealloc(h->data, strlen(h->data) + 2);
3014
3015
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
3016
		    rcfile_error(N_("Unable to write ~/.nano_history file: %s\n"), strerror(errno));
3017
3018
3019
		    goto come_from;
		}
	    }
3020
  come_from:
3021
3022
3023
3024
3025
	    fclose(hist);
	}
	free(nanohist);
    }
}
3026
#endif /* !NANO_SMALL && ENABLE_NANORC */