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

22
23
#include "config.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

331
332
333
334
335
336
#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

337
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
338
339
340
341

    return 1;
}

Chris Allegretta's avatar
Chris Allegretta committed
342
/* Open the file (and decide if it exists). */
Chris Allegretta's avatar
Chris Allegretta committed
343
int 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);
Chris Allegretta's avatar
Chris Allegretta committed
352
353
354
355
356
357
	    return -1;
	} else {
	    /* We have a new file */
	    statusbar(_("New File"));
	    new_file();
	}
358
359
360
361
362
363
364
365
    } else if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
		S_ISBLK(fileinfo.st_mode)) {
	/* Don't open character or block files.  Sorry, /dev/sndstat! */
	statusbar(S_ISDIR(fileinfo.st_mode) ? _("\"%s\" is a directory") :
			_("File \"%s\" is a device file"), filename);
	if (!insert)
	    new_file();
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
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
		)
Chris Allegretta's avatar
Chris Allegretta committed
374
	    statusbar("%s: %s", strerror(errno), filename);
375
376
	if (!insert)
	    new_file();
Chris Allegretta's avatar
Chris Allegretta committed
377
378
379
380
	return -1;
    } 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
386
387
	    return -1;
	}
	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
394
395
    }

    return 1;
}

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

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

409
    while (TRUE) {
410
411

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

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

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

    return buf;
}

427
void do_insertfile(int loading_file)
Chris Allegretta's avatar
Chris Allegretta committed
428
{
Chris Allegretta's avatar
Chris Allegretta committed
429
    int i, old_current_x = current_x;
430
    char *realname = NULL;
431
432
    static char *inspath = NULL;

433
434
435
436
    if (inspath == NULL) {
	inspath = charalloc(1);
	inspath[0] = '\0';
    }
Chris Allegretta's avatar
Chris Allegretta committed
437

438
#ifndef DISABLE_WRAPPING
Chris Allegretta's avatar
Chris Allegretta committed
439
    wrap_reset();
440
#endif
441

442
  start_again:
443
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
444
    currshortcut = insertfile_list;
445
446
#endif

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

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

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

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

500
	if (i == NANO_EXTCMD_KEY) {
501
502
	    char *ans = mallocstrcpy(NULL, answer);
	    int ts = statusq(TRUE, extcmd_list, ans, NULL, 
503
		_("Command to execute"));
504
505
506

	    free(ans);

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

518
519
	    if (tmp != NULL) {
		free(answer);
520
		answer = tmp;
521
		resetstatuspos = 1;
522
	    } else
523
524
525
526
527
		goto start_again;
	}
#endif

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

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

551
#ifndef NANO_SMALL
552
553
	if (i == NANO_EXTCMD_KEY) {
	    realname = mallocstrcpy(realname, "");
554
	    i = open_pipe(answer);
555
	} else
556
#endif /* NANO_SMALL */
557
558
	{
	    realname = real_dir_from_tilde(answer);
559
	    i = open_file(realname, 1, loading_file);
560
	}
561

562
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
563
564
565
566
567
568
569
570
571
572
573
574
575
576
	if (loading_file) {
	    /* if there was an error opening the file, free() realname,
	       free() fileage (which now points to the new buffer we
	       created to hold the file), reload the buffer we had open
	       before, and skip the insertion; otherwise, save realname
	       in filename and continue the insertion */
	    if (i == -1) {
		free(realname);
		free(fileage);
		load_open_file();
		goto skip_insert;
	    } else
		filename = mallocstrcpy(filename, realname);
	}
577
578
#endif

579
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
580

Chris Allegretta's avatar
Chris Allegretta committed
581
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
582
	dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
583
#endif
584

585
#ifdef ENABLE_MULTIBUFFER
586
	if (loading_file)
587
	    load_file(FALSE);
588
589
590
	else
#endif
	    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
591

592
#ifdef ENABLE_MULTIBUFFER
593
594
595
596
597
598
	/* 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 */
599
	    shortcut_init(FALSE);
600
601
602
	}
#endif

Chris Allegretta's avatar
Chris Allegretta committed
603
604
605
606
607
608
609
610
611
612
613
#ifdef ENABLE_MULTIBUFFER
	if (!loading_file) {
#endif

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

#ifdef ENABLE_MULTIBUFFER
	}
#endif

614
	/* If we've gone off the bottom, recenter; otherwise, just redraw */
615
	edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
616
617
618

    } else {
	statusbar(_("Cancelled"));
619
	i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
620
    }
621

Chris Allegretta's avatar
Chris Allegretta committed
622
623
624
625
#ifdef ENABLE_MULTIBUFFER
  skip_insert:
#endif

626
627
628
629
    free(inspath);
    inspath = NULL;

    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
630
631
}

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

    display_main_list();
}

650
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
651
652
653
/* Create a new openfilestruct node. */
openfilestruct *make_new_opennode(openfilestruct *prevnode)
{
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
654
    openfilestruct *newnode = (openfilestruct *)nmalloc(sizeof(openfilestruct));
Chris Allegretta's avatar
Chris Allegretta committed
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709

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

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

    return newnode;
}

/* Splice a node into an existing openfilestruct. */
void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
		     openfilestruct *end)
{
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

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

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

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

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

/* Deallocate all memory associated with this and later files,
 * including the lines of text. */
void free_openfilestruct(openfilestruct *src)
{
    if (src != NULL) {
	while (src->next != NULL) {
	    src = src->next;
	    delete_opennode(src->prev);
#ifdef DEBUG
710
	    fprintf(stderr, "%s: free'd a node, YAY!\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
711
712
713
714
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
715
	fprintf(stderr, "%s: free'd last node.\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
716
717
718
719
#endif
    }
}

720
/*
721
 * Add/update an entry to the open_files openfilestruct.  If update is
722
723
 * FALSE, a new entry is created; otherwise, the current entry is
 * updated.
724
 */
725
void add_open_file(int update)
726
{
727
    openfilestruct *tmp;
728

729
    if (fileage == NULL || current == NULL || filename == NULL)
730
	return;
731
732

    /* if no entries, make the first one */
733
    if (open_files == NULL)
734
	open_files = make_new_opennode(NULL);
735
736
737
738
739
740
741

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

745
746
	tmp = make_new_opennode(NULL);
	splice_opennode(open_files, tmp, open_files->next);
747
748
749
750
	open_files = open_files->next;
    }

    /* save current filename */
751
    open_files->filename = mallocstrcpy(open_files->filename, filename);
752

753
754
755
756
757
#ifndef NANO_SMALL
    /* save the file's stat */
    open_files->originalfilestat = originalfilestat;
#endif

758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
    /* 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 */
774
    open_files->file_lineno = current->lineno;
775

776
777
778
779
    /* start with default modification status: unmodified (and marking
       status, if available: unmarked) */
    open_files->file_flags = 0;

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

794
795
796
    /* 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
797
798
799
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
800
    }
801
802

#ifdef DEBUG
803
    fprintf(stderr, "filename is %s\n", open_files->filename);
804
805
806
807
808
#endif
}

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

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

Chris Allegretta's avatar
Chris Allegretta committed
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
    /* 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
843

Chris Allegretta's avatar
Chris Allegretta committed
844
845
846
847
#ifdef ENABLE_COLOR
    update_color();
#endif

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

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

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

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

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

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

881
    if (open_files->prev != NULL) {
882
883
884
	open_files = open_files->prev;

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

    }

890
    else if (open_files->next != NULL) {
891
892

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

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

    }

    load_open_file();

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

908
909
910
911
912
#ifdef DEBUG
    dump_buffer(current);
#endif
}

913
void open_prevfile_void(void)
914
{
915
    open_prevfile(FALSE);
916
917
}

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

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

933
    if (open_files->prev == NULL && open_files->next == NULL) {
934
935
936

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

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

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

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

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

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

	}
    }

    load_open_file();

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

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

973
void open_nextfile_void(void)
974
{
975
    open_nextfile(FALSE);
976
977
}

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

987
    if (open_files == NULL)
988
989
	return 1;

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

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

1004
1005
    unlink_opennode(tmp);
    delete_opennode(tmp);
1006

1007
    shortcut_init(FALSE);
1008
1009
1010
    display_main_list();
    return 0;
}
1011
#endif /* MULTIBUFFER */
1012

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

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

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

1038
    if (d_here != NULL) {
1039
1040

	align(&d_here);
1041
	if (strcmp(d_here, "/")) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1042
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1043
1044
	    strcat(d_here, "/");
	}
1045
1046
1047
1048
1049

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

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

1058
1059
1060
1061
1062
1063
	/* 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
1064
		d_there = charealloc(d_there, strlen(d_there) + 2);
1065
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1066
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1067
1068
1069
1070
		strcat(d_there_file, "/");
	    }
	}

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

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

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

		free(d_there);

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

		align(&d_there);
1109
		if (d_there != NULL) {
1110
1111
1112
1113

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
		    if (strcmp(d_there, "/")) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1114
			d_there = charealloc(d_there, strlen(d_there) + 2);
1115
1116
			strcat(d_there, "/");
		    }
1117
1118
1119
		}
		else
		    return NULL;
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
	    }

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

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

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

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

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

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

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

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

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

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

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

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

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

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1227
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1228
1229

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

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

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

1254
    if (operating_dir == NULL)
1255
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1256

1257
1258
1259
    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
1260
     * inaccessible, unset operating_dir. */
1261
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1262
1263
1264
1265
1266
1267
1268
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1269
/* Check to see if we're inside the operating directory.  Return 0 if we
1270
1271
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1272
 * completion will work. */
1273
int check_operating_dir(const char *currpath, int allow_tabcomp)
1274
{
1275
1276
1277
1278
    /* 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. */
1279

1280
1281
1282
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1283

1284
    /* If no operating directory is set, don't bother doing anything. */
1285
    if (operating_dir == NULL)
1286
	return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1287

1288
    assert(full_operating_dir != NULL);
1289
1290

    fullpath = get_full_path(currpath);
1291
1292
1293
1294
1295
1296
1297
1298

    /* 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. */
1299
    if (fullpath == NULL)
1300
	return allow_tabcomp;
1301
1302
1303
1304
1305

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

1306
1307
1308
1309
1310
    /* 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. */
1311
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1312
1313
	retval = 1;
    free(fullpath);	
1314
1315

    /* Otherwise, we're still inside it. */
1316
    return retval;
1317
}
1318
1319
#endif

1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
#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

1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
/* 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;
}

/* Write a file out.  If tmp is nonzero, we set the umask to disallow
 * 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
1376
 *
1377
1378
 * 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.
1379
 *
1380
1381
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1382
 *
1383
1384
 * nonamechange means don't change the current filename.  It is ignored
 * if tmp is FALSE or if we're appending/prepending.
1385
1386
 *
 * Return -1 on error, 1 on success. */
1387
int write_file(const char *name, int tmp, int append, int nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1388
{
1389
1390
    int retval = -1;
	/* Instead of returning in this function, you should always
1391
1392
1393
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
    const filestruct *fileptr = fileage;
1394
    int fd;
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
    mode_t original_umask = 0;
	/* Our umask, from when nano started. */
    int realexists;
	/* The result of stat().  True if the file exists, false
	 * otherwise.  If name is a link that points nowhere, realexists
	 * is false. */
    struct stat st;
	/* The status fields filled in by stat(). */
    int anyexists;
	/* Result of lstat().  Same as realexists unless name is a
	 * link. */
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
	/* name after ~ expansion. */
    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
1414

1415
    assert(name != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1416
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1417
1418
1419
	statusbar(_("Cancelled"));
	return -1;
    }
1420
1421
    if (!tmp)
	titlebar(NULL);
1422

1423
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1424

1425
#ifndef DISABLE_OPERATINGDIR
1426
    /* If we're writing a temporary file, we're probably going outside
1427
     * the operating directory, so skip the operating directory test. */
1428
    if (!tmp && check_operating_dir(realname, FALSE) != 0) {
1429
1430
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1431
1432
1433
    }
#endif

1434
1435
1436
1437
1438
1439
1440
    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)) {
1441
	statusbar(_("Cannot prepend or append to a symlink with --nofollow set"));
1442
1443
1444
	goto cleanup_and_exit;
    }

1445
    /* Save the state of file at the end of the symlink (if there is
1446
1447
     * one). */
    realexists = stat(realname, &st) != -1;
1448

1449
1450
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1451
1452
1453
1454
1455
1456
1457
1458
     * 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)) {

1459
	FILE *backup_file;
1460
	char *backupname;
1461
	struct utimbuf filetime;
1462
	int copy_status;
1463

1464
	/* Save the original file's access and modification times. */
1465
1466
1467
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1468
	/* Open the original file to copy to the backup. */
1469
	f = fopen(realname, "rb");
1470
	if (f == NULL) {
1471
	    statusbar(_("Error reading %s: %s"), realname,
1472
		strerror(errno));
1473
	    goto cleanup_and_exit;
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
1499
1500
1501
1502
1503
1504
1505
1506
1507
	/* 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);
	}
1508

1509
1510
1511
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1512
	backup_file = fopen(backupname, "wb");
1513
1514
1515
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
	    statusbar(_("Error writing %s: %s"), backupname, strerror(errno));
1516
	    free(backupname);
1517
1518
1519
1520
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1521
1522
1523
	}

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

1527
1528
1529
1530
1531
1532
1533
1534
1535
	/* 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)
		statusbar(_("Error reading %s: %s"), realname,
1536
			strerror(errno));
1537
1538
1539
1540
1541
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1542
1543
	free(backupname);
    }
1544
#endif /* !NANO_SMALL */
1545

1546
1547
1548
1549
1550
1551
    /* 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));
1552
	goto cleanup_and_exit;
1553
    }
1554

1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
    original_umask = umask(0);
    umask(original_umask);
    /* If we create a temp file, we don't let anyone else access it.  We
     * create a temp file if tmp is nonzero or if we prepend. */
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

    /* If we are prepending, copy the file to a temp file. */
    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);
1580
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1581
	}
1582

1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
	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);
1599
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1600
1601
1602
	}
    }

1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
    /* Now open the file in place.  Use O_EXCL if tmp is nonzero.  This
     * is now copied from joe, because wiggy says so *shrug*. */
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
	S_IRUSR | S_IWUSR);

    /* Put the umask back to the user's original value. */
    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;
    }
1618

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1619
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1620
    if (f == NULL) {
1621
1622
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1623
	goto cleanup_and_exit;
1624
1625
    }

1626
    /* There might not be a magicline.  There won't be when writing out
1627
1628
1629
1630
1631
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1632

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

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

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

1641
	if (size < data_len) {
1642
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1643
	    fclose(f);
1644
	    goto cleanup_and_exit;
1645
	}
1646
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1647
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1648
1649
1650
1651
1652
	    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
1653
1654

	if (!ISSET(MAC_FILE))
1655
#endif
1656
1657
1658
1659
1660
	    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
1661
1662
1663
1664
1665

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

1666
    /* If we're prepending, open the temp file, and append it to f. */
1667
    if (append == 2) {
1668
1669
1670
1671
1672
1673
1674
1675
	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);
1676
	}
1677
	if (f_source == NULL) {
1678
1679
	    statusbar(_("Error reading %s: %s"), tempname, strerror(errno));
	    fclose(f);
1680
	    goto cleanup_and_exit;
1681
1682
	}

1683
1684
1685
	if (copy_file(f_source, f) == -1
		|| unlink(tempname) == -1) {
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1686
	    goto cleanup_and_exit;
1687
	}
1688
1689
1690
1691
    } 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
1692
    }
1693

1694
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1695
	if (!nonamechange) {
1696
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1697
1698
#ifdef ENABLE_COLOR
	    update_color();
1699
1700
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1701
1702
#endif
	}
1703

1704
1705
1706
1707
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1708
1709
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1710
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1711
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1712
    }
1713
1714
1715
1716
1717

    retval = 1;

  cleanup_and_exit:
    free(realname);
1718
    free(tempname);
1719
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1720
1721
}

1722
1723
1724
#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
1725
1726
1727
1728
1729
 * 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)
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
1762
1763
1764
1765
1766
1767
1768
1769
1770
{
    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;

1771
    retval = write_file(name, tmp, append, TRUE);
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784

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

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

1785
int do_writeout(int exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1786
{
1787
1788
    int i;
    int append = 0;
1789
#ifdef NANO_EXTRA
1790
    static int did_cred = FALSE;
1791
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1792

1793
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1794
    currshortcut = writefile_list;
1795
1796
#endif

1797
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1798
1799
1800
1801
1802
	i = write_file(filename, FALSE, 0, FALSE);
	if (i == 1) {
	    /* Write succeeded. */
	    display_main_list();
	    return 1;
1803
	}
Chris Allegretta's avatar
Chris Allegretta committed
1804
1805
    }

1806
#ifndef NANO_SMALL
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
    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);
1817
1818
	const char *formatstr, *backupstr;

1819
1820
1821
1822
1823
1824
1825
	if (ISSET(MAC_FILE))
	   formatstr = _(" [Mac Format]");
	else if (ISSET(DOS_FILE))
	   formatstr = _(" [DOS Format]");
	else
	   formatstr = "";

1826
1827
1828
1829
1830
	if (ISSET(BACKUP_FILE))
	   backupstr = _(" [Backup]");
	else
	   backupstr = "";

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

	i = statusq(TRUE, writefile_list,
#ifndef NANO_SMALL
		ans, NULL, "%s%s%s", msg, formatstr, backupstr
#else
		filename, "%s", msg
#endif
		);

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

1860
1861
1862
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
1863
	    return -1;
1864
	}
Chris Allegretta's avatar
Chris Allegretta committed
1865

1866
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1867
	if (i == NANO_TOFILES_KEY) {
1868
	    char *tmp = do_browse_from(answer);
1869

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

Chris Allegretta's avatar
Chris Allegretta committed
1899
#ifdef DEBUG
1900
	fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1901
#endif
1902
1903

#ifdef NANO_EXTRA
1904
	if (exiting && !ISSET(TEMP_FILE) && !strcasecmp(answer, "zzy")
1905
		&& !did_cred) {
1906
	    do_credits();
1907
	    did_cred = TRUE;
1908
1909
	    return -1;
	}
1910
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1911
	if (append == 0 && strcmp(answer, filename)) {
1912
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1913

1914
	    if (!stat(answer, &st)) {
1915
		i = do_yesno(FALSE, _("File exists, OVERWRITE ? "));
1916
1917
		if (i == 0 || i == -1)
		    continue;
1918
1919
1920
1921
1922
	    /* 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. */
1923
	    } else if (!ISSET(RESTRICTED) && filename[0] != '\0'
1924
#ifndef NANO_SMALL
1925
		&& (exiting || !ISSET(MARK_ISSET))
1926
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1927
		) {
1928
		i = do_yesno(FALSE, _("Save file under DIFFERENT NAME ?" ));
1929
1930
1931
		if (i == 0 || i == -1)
		    continue;
	    }
1932
	}
Chris Allegretta's avatar
Chris Allegretta committed
1933

1934
#ifndef NANO_SMALL
1935
1936
1937
1938
	/* 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. */
1939
	if (!ISSET(RESTRICTED) && !exiting && ISSET(MARK_ISSET))
1940
	    i = write_marked(answer, FALSE, append);
1941
	else
1942
#endif /* !NANO_SMALL */
1943
	    i = write_file(answer, FALSE, append, FALSE);
1944

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

1956
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1957
{
1958
    do_writeout(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
1959
}
Chris Allegretta's avatar
Chris Allegretta committed
1960

1961
/* Return a malloc()ed string containing the actual directory, used
1962
1963
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1964
{
Chris Allegretta's avatar
Chris Allegretta committed
1965
    char *dirtmp = NULL;
1966

1967
    if (buf[0] == '~') {
1968
1969
1970
1971
1972
1973
1974
	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++)
	    ;

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

1987
	if (userdata != NULL) {	/* User found */
1988
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1989
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1990
	}
1991
    }
1992

1993
1994
1995
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1996
    return dirtmp;
1997
1998
}

Chris Allegretta's avatar
Chris Allegretta committed
1999
#ifndef DISABLE_TABCOMP
2000
2001
2002
/* Tack a slash onto the string we're completing if it's a directory.  We
 * assume there is room for one more character on the end of buf.  The
 * return value says whether buf is a directory. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2003
int append_slash_if_dir(char *buf, int *lastwastab, int *place)
2004
{
2005
    char *dirptr = real_dir_from_tilde(buf);
2006
    struct stat fileinfo;
2007
    int ret = 0;
2008

2009
    assert(dirptr != buf);
2010

2011
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
2012
	strncat(buf, "/", 1);
2013
	(*place)++;
2014
	/* now we start over again with # of tabs so far */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2015
	*lastwastab = 0;
2016
	ret = 1;
2017
2018
    }

2019
    free(dirptr);
2020
    return ret;
2021
}
Chris Allegretta's avatar
Chris Allegretta committed
2022
2023

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

2048
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2049
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2050

2051
    strcat(buf, "*");
2052

2053
    while ((userdata = getpwent()) != NULL) {
2054

2055
	if (check_wildcard_match(userdata->pw_name, &buf[1]) == TRUE) {
2056
2057
2058
2059

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

2061
2062
2063
2064
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2065
	    if (operating_dir != NULL) {
2066
		if (check_operating_dir(userdata->pw_dir, TRUE) != 0)
2067
2068
2069
2070
		    continue;
	    }
#endif

2071
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2072
2073
2074
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
	    ++*num_matches;
2075

2076
2077
2078
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2079
	}
2080
2081
    }
    endpwent();
2082

2083
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2084
2085
2086
2087
2088
2089
2090
}

/* 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
2091
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2092
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2093
2094
2095
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2096
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2097
2098
2099
2100

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

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

Chris Allegretta's avatar
Chris Allegretta committed
2108
2109
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2110
2111
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2112

Chris Allegretta's avatar
Chris Allegretta committed
2113
    } else {
2114
2115

#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2116
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
2117
#else
2118
	/* The better, but apparently segfault-causing way */
Chris Allegretta's avatar
Chris Allegretta committed
2119
	if ((dirname = getcwd(NULL, 0)) == NULL)
2120
#endif /* PATH_MAX */
Chris Allegretta's avatar
Chris Allegretta committed
2121
2122
2123
2124
2125
2126
	    return matches;
	else
	    tmp = buf;
    }

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

Chris Allegretta's avatar
Chris Allegretta committed
2132
2133
2134
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2135
2136

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


Chris Allegretta's avatar
Chris Allegretta committed
2143
    dir = opendir(dirname);
2144
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2145
2146
2147
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2148
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2149
2150
2151
2152
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2153
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2154
2155
2156
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
2157
2158
2159
2160

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2161
2162
2163
2164
2165
2166
2167
2168

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

2169
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2170
2171
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2172
2173
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2174
		if (check_operating_dir(tmp2, TRUE) != 0) {
2175
2176
2177
2178
2179
2180
2181
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2182
	    tmp2 = NULL;
2183
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2184
2185
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2186
	    ++*num_matches;
2187
2188
2189
2190

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2191
2192
	}
    }
2193
2194
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2195

2196
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2197
2198
}

Chris Allegretta's avatar
Chris Allegretta committed
2199
2200
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2201
char *input_tab(char *buf, int place, int *lastwastab, int *newplace, int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2202
2203
{
    /* Do TAB completion */
2204
    static int num_matches = 0, match_matches = 0;
2205
    static char **matches = (char **)NULL;
2206
    int pos = place, i = 0, col = 0, editline = 0;
2207
    int longestname = 0, is_dir = 0;
2208
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2209

2210
2211
    *list = 0;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2212
    if (*lastwastab == FALSE) {
2213
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2214

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2215
	*lastwastab = 1;
2216

Chris Allegretta's avatar
Chris Allegretta committed
2217
2218
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2219
2220
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2221

2222
2223
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2224
2225

	/* skip any leading white space */
2226
	while (*tmp && isblank(*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2227
2228
2229
2230
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2231
2232
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2233
	    free(matches);
2234
	    matches = (char **)NULL;
2235
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2236
2237
2238
2239
2240
	}

	/* 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
2241
2242
2243
2244
2245
	/* 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. */
2246
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2247
	    buf = mallocstrcpy(buf, tmp);
2248
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2249
	}
2250
2251
2252
2253
2254
2255
	/* 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
2256
2257
2258

	/* Try to match everything in the current working directory that
	 * matches.  */
2259
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2260
2261
2262
	    matches = cwd_tab_completion(tmp, &num_matches);

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

2265
2266
2267
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2268
	/* Did we find exactly one match? */
2269
	switch (num_matches) {
2270
	case 0:
2271
	    blank_edit();
2272
	    wrefresh(edit);
2273
2274
	    break;
	case 1:
2275

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

2278
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2279
2280
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2281
		tmp++;
2282
	    } else
2283
2284
		tmp = buf;

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

2288
	    if (is_dir != 0)
2289
		break;
2290
2291

	    copyto = tmp;
2292
2293
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2294
2295
		tmp++;

2296
	    /* write out the matched name */
2297
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2298
2299
	    *newplace += strlen(matches[0]) - pos;

2300
2301
2302
2303
2304
2305
2306
	    /* 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;

2307
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2308
	    append_slash_if_dir(buf, lastwastab, newplace);
2309

2310
2311
	    break;
	default:
2312
	    /* Check to see if all matches share a beginning, and, if so,
2313
	       tack it onto buf and then beep */
2314

2315
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2316
2317
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2318
		tmp++;
2319
	    } else
2320
2321
		tmp = buf;

2322
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2323
		 pos <= strlen(matches[0]); pos++)
2324
2325
		tmp++;

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

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

2359
	    editline = 0;
2360

2361
2362
2363
2364
2365
2366
2367
2368
	    /* 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;

2369
	    foo = charalloc(longestname + 5);
2370

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

2374
		/* make each filename shown be the same length as the longest
2375
		   filename, with two spaces at the end */
2376
2377
2378
2379
2380
2381
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2382
2383
		/* Disable el cursor */
		curs_set(0);
2384
2385
2386
2387
2388
2389
		/* 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 */
2390
		if (col > COLS - longestname && i + 1 < num_matches) {
2391
2392
		    editline++;
		    wmove(edit, editline, 0);
2393
2394
2395
2396
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2397
2398
2399
		    col = 0;
		}
	    }
2400
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2401
	    wrefresh(edit);
2402
	    *list = 1;
2403
2404
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2405
2406
    }

2407
2408
2409
2410
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
    if (*list == 0)
	edit_refresh();
2411
2412
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2413
}
2414
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2415

2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
/* 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;
}

2431
#ifndef DISABLE_BROWSER
2432
/* Our sort routine for file listings -- sort directories before
2433
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2434
2435
int diralphasort(const void *va, const void *vb)
{
2436
2437
2438
2439
    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
2440

2441
    if (aisdir != 0 && bisdir == 0)
2442
	return -1;
2443
    if (aisdir == 0 && bisdir != 0)
2444
	return 1;
2445

2446
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2447
2448
}

2449
/* Free our malloc()ed memory */
2450
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2451
{
2452
2453
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2454
2455
2456
    free(array);
}

2457
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2458
2459
void striponedir(char *foo)
{
2460
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2461

2462
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2463
    /* Don't strip the root dir */
2464
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2465
2466
	return;

2467
2468
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2469
    if (*tmp == '/')
2470
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2471
2472
2473
2474
2475

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

    if (tmp != foo)
2476
2477
2478
2479
2480
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2481
    }
Chris Allegretta's avatar
Chris Allegretta committed
2482
2483
}

2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
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;
}

2494
/* Initialize the browser code, including the list of files in *path */
2495
char **browser_init(const char *path, int *longest, int *numents)
2496
2497
2498
{
    DIR *dir;
    struct dirent *next;
2499
    char **filelist;
2500
    int i = 0;
2501
    size_t path_len;
2502
2503

    dir = opendir(path);
2504
    if (dir == NULL)
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
	return NULL;

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

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

2520
2521
2522
2523
    if (!strcmp(path, "/"))
	path = "";
    path_len = strlen(path);

2524
2525
2526
2527
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;

2528
2529
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2530
2531
	i++;
    }
2532
    closedir(dir);
2533
2534
2535
2536
2537
2538
2539

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2540
/* Our browser function.  inpath is the path to start browsing from */
2541
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2542
2543
2544
2545
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2546
    int numents = 0, i = 0, j = 0, kbinput = ERR, meta_key, longest = 0;
2547
2548
    int abort = 0, col = 0, selected = 0, editline = 0, width = 0;
    int filecols = 0, lineno = 0;
2549
    char **filelist = (char **)NULL;
2550
#ifndef DISABLE_MOUSE
2551
2552
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2553

2554
2555
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2556
2557
2558
    /* If path isn't the same as inpath, we are being passed a new
	dir as an arg.  We free it here so it will be copied from 
	inpath below */
Chris Allegretta's avatar
Chris Allegretta committed
2559
2560
2561
2562
2563
    if (path != NULL && strcmp(path, inpath)) {
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2564
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2565
    if (path == NULL)
2566
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2567
2568

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

2571
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2572
2573
2574
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2575
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2576
2577
2578
2579
2580
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2581
2582

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2583
    do {
2584
2585
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2586

2587
	check_statblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2588

2589
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2590
	currshortcut = browser_list;
2591
2592
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2593
2594
 	editline = 0;
	col = 0;
2595
	    
2596
	/* Compute line number we're on now, so we don't divide by zero later */
2597
2598
2599
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2600
2601

	switch (kbinput) {
2602

2603
#ifndef DISABLE_MOUSE
2604
	case KEY_MOUSE:
2605
	    if (getmouse(&mevent) == ERR)
2606
		return retval;
2607
2608
2609
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2610
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2611
2612
2613
2614
		int selectedbackup = selected;

		mevent.y -= 2;

2615
2616
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2617
		selected = (lineno / editwinrows) * editwinrows * width
2618
2619
2620
2621
2622
2623
			+ 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--;
2624
2625
2626

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2627
		if (selected > numents - 1)
2628
		    selected = numents - 1;
2629
		else if (selectedbackup == selected)
2630
2631
2632
2633
		    ungetch('s');	/* Unget the 'select' key */
	    } else	/* Must be clicking a shortcut */
		do_mouse();

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

2683
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2684
2685
2686
	    /* 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. */
2687
	    if (check_operating_dir(filelist[selected], FALSE) != 0) {
2688
2689
2690
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		beep();
		break;
2691
2692
2693
	    }
#endif

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2883
2884
2885
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2886

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

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

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

#ifndef NANO_SMALL
#ifdef ENABLE_NANORC
void load_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2922
    const struct passwd *userage = NULL;
2923
2924
    static char *nanohist;
    char *buf, *ptr;
Chris Allegretta's avatar
Chris Allegretta committed
2925
    char *homenv = getenv("HOME");
2926
2927
2928
    historyheadtype *history = &search_history;


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

    /* assume do_rcfile has reported missing home dir */

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

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

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

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