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

22
23
#include "config.h"

Chris Allegretta's avatar
Chris Allegretta committed
24
25
26
27
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
/* 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;
}

1372
/* Write a file out.  If tmp is FALSE, we set the umask to disallow
1373
1374
1375
 * 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
    mode_t original_umask = 0;
1396
1397
	/* Our umask, from when nano started. */
    int realexists;
1398
	/* The result of stat().  TRUE if the file exists, FALSE
1399
	 * otherwise.  If name is a link that points nowhere, realexists
1400
	 * is FALSE. */
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
    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
1442
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1443
1444
1445
	goto cleanup_and_exit;
    }

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

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

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

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

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

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

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

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

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

1556
1557
    original_umask = umask(0);
    umask(original_umask);
1558

1559
    /* If we create a temp file, we don't let anyone else access it.  We
1560
     * create a temp file if tmp is TRUE or if we're prepending. */
1561
1562
1563
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

1564
    /* If we're prepending, copy the file to a temp file. */
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
    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);
1582
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1583
	}
1584

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

1605
1606
    /* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This is
     * now copied from joe, because wiggy says so *shrug*. */
1607
1608
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
1609
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1610

1611
    /* Set the umask back to the user's original value. */
1612
1613
1614
1615
1616
1617
1618
1619
    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;
    }
1620

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

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

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

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

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

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

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

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

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

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

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

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

    retval = 1;

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

1724
1725
1726
#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
1727
1728
1729
1730
1731
 * 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)
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
1771
1772
{
    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;

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

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

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

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

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

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

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

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

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

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

1850
1851
1852
1853
1854
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
	i = statusq(!ISSET(RESTRICTED) || filename[0] == '\0' ? TRUE :
		FALSE, writefile_list,
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
#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
1865

1866
1867
1868
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
1869
	    return -1;
1870
	}
Chris Allegretta's avatar
Chris Allegretta committed
1871

1872
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1873
	if (i == NANO_TOFILES_KEY) {
1874
	    char *tmp = do_browse_from(answer);
1875

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

Chris Allegretta's avatar
Chris Allegretta committed
1905
#ifdef DEBUG
1906
	fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1907
#endif
1908
1909

#ifdef NANO_EXTRA
1910
	if (exiting && !ISSET(TEMP_FILE) && !strcasecmp(answer, "zzy")
1911
		&& !did_cred) {
1912
	    do_credits();
1913
	    did_cred = TRUE;
1914
1915
	    return -1;
	}
1916
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1917
	if (append == 0 && strcmp(answer, filename)) {
1918
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1919

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

1940
#ifndef NANO_SMALL
1941
1942
1943
1944
	/* 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. */
1945
	if (!ISSET(RESTRICTED) && !exiting && ISSET(MARK_ISSET))
1946
	    i = write_marked(answer, FALSE, append);
1947
	else
1948
#endif /* !NANO_SMALL */
1949
	    i = write_file(answer, FALSE, append, FALSE);
1950

1951
#ifdef ENABLE_MULTIBUFFER
1952
	/* If we're not about to exit, update the current entry in
1953
	 * the open_files structure. */
1954
	if (!exiting)
1955
	    add_open_file(TRUE);
1956
#endif
1957
1958
	display_main_list();
	return i;
1959
    } /* while (TRUE) */
Chris Allegretta's avatar
Chris Allegretta committed
1960
1961
}

1962
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1963
{
1964
    do_writeout(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
1965
}
Chris Allegretta's avatar
Chris Allegretta committed
1966

1967
/* Return a malloc()ed string containing the actual directory, used
1968
1969
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1970
{
Chris Allegretta's avatar
Chris Allegretta committed
1971
    char *dirtmp = NULL;
1972

1973
    if (buf[0] == '~') {
1974
1975
1976
1977
1978
1979
1980
	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++)
	    ;

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

1993
	if (userdata != NULL) {	/* User found */
1994
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1995
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1996
	}
1997
    }
1998

1999
2000
2001
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

2002
    return dirtmp;
2003
2004
}

Chris Allegretta's avatar
Chris Allegretta committed
2005
#ifndef DISABLE_TABCOMP
2006
2007
2008
/* 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
2009
int append_slash_if_dir(char *buf, int *lastwastab, int *place)
2010
{
2011
    char *dirptr = real_dir_from_tilde(buf);
2012
    struct stat fileinfo;
2013
    int ret = 0;
2014

2015
    assert(dirptr != buf);
2016

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

2025
    free(dirptr);
2026
    return ret;
2027
}
Chris Allegretta's avatar
Chris Allegretta committed
2028
2029

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

2054
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2055
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2056

2057
    strcat(buf, "*");
2058

2059
    while ((userdata = getpwent()) != NULL) {
2060

2061
	if (check_wildcard_match(userdata->pw_name, &buf[1]) == TRUE) {
2062
2063
2064
2065

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

2067
2068
2069
2070
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2071
	    if (operating_dir != NULL) {
2072
		if (check_operating_dir(userdata->pw_dir, TRUE) != 0)
2073
2074
2075
2076
		    continue;
	    }
#endif

2077
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2078
2079
2080
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
	    ++*num_matches;
2081

2082
2083
2084
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2085
	}
2086
2087
    }
    endpwent();
2088

2089
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2090
2091
2092
2093
2094
2095
2096
}

/* 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
2097
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2098
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2099
2100
2101
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2102
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2103
2104
2105
2106

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

2107
    /* Okie, if there's a / in the buffer, strip out the directory part */
2108
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2109
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2110
2111
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2112
	    tmp--;
2113

Chris Allegretta's avatar
Chris Allegretta committed
2114
2115
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2116
2117
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2118

Chris Allegretta's avatar
Chris Allegretta committed
2119
    } else {
2120
2121

#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2122
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
2123
#else
2124
	/* The better, but apparently segfault-causing way */
Chris Allegretta's avatar
Chris Allegretta committed
2125
	if ((dirname = getcwd(NULL, 0)) == NULL)
2126
#endif /* PATH_MAX */
Chris Allegretta's avatar
Chris Allegretta committed
2127
2128
2129
2130
2131
2132
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2133
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2134
2135
2136
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2137

Chris Allegretta's avatar
Chris Allegretta committed
2138
2139
2140
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2141
2142

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2143
    fprintf(stderr, "\nDir = %s\n", dirname);
2144
2145
2146
2147
2148
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2149
    dir = opendir(dirname);
2150
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2151
2152
2153
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2154
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2155
2156
2157
2158
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2159
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2160
2161
2162
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
2163
2164
2165
2166

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2167
2168
2169
2170
2171
2172
2173
2174

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

2175
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2176
2177
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2178
2179
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2180
		if (check_operating_dir(tmp2, TRUE) != 0) {
2181
2182
2183
2184
2185
2186
2187
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2188
	    tmp2 = NULL;
2189
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2190
2191
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2192
	    ++*num_matches;
2193
2194
2195
2196

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2197
2198
	}
    }
2199
2200
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2201

2202
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2203
2204
}

Chris Allegretta's avatar
Chris Allegretta committed
2205
2206
/* 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
2207
char *input_tab(char *buf, int place, int *lastwastab, int *newplace, int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2208
2209
{
    /* Do TAB completion */
2210
    static int num_matches = 0, match_matches = 0;
2211
    static char **matches = (char **)NULL;
2212
    int pos = place, i = 0, col = 0, editline = 0;
2213
    int longestname = 0, is_dir = 0;
2214
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2215

2216
2217
    *list = 0;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2218
    if (*lastwastab == FALSE) {
2219
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2220

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2221
	*lastwastab = 1;
2222

Chris Allegretta's avatar
Chris Allegretta committed
2223
2224
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2225
2226
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2227

2228
2229
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2230
2231

	/* skip any leading white space */
2232
	while (*tmp && isblank(*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2233
2234
2235
2236
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2237
2238
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2239
	    free(matches);
2240
	    matches = (char **)NULL;
2241
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2242
2243
2244
2245
2246
	}

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

	/* Try to match everything in the current working directory that
	 * matches.  */
2265
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2266
2267
2268
	    matches = cwd_tab_completion(tmp, &num_matches);

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

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

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

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

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

2294
	    if (is_dir != 0)
2295
		break;
2296
2297

	    copyto = tmp;
2298
2299
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2300
2301
		tmp++;

2302
	    /* write out the matched name */
2303
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2304
2305
	    *newplace += strlen(matches[0]) - pos;

2306
2307
2308
2309
2310
2311
2312
	    /* 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;

2313
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2314
	    append_slash_if_dir(buf, lastwastab, newplace);
2315

2316
2317
	    break;
	default:
2318
	    /* Check to see if all matches share a beginning, and, if so,
2319
	       tack it onto buf and then beep */
2320

2321
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2322
2323
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2324
		tmp++;
2325
	    } else
2326
2327
		tmp = buf;

2328
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2329
		 pos <= strlen(matches[0]); pos++)
2330
2331
		tmp++;

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

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

2365
	    editline = 0;
2366

2367
2368
2369
2370
2371
2372
2373
2374
	    /* 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;

2375
	    foo = charalloc(longestname + 5);
2376

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

2380
		/* make each filename shown be the same length as the longest
2381
		   filename, with two spaces at the end */
2382
2383
2384
2385
2386
2387
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

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

2413
2414
2415
2416
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
    if (*list == 0)
	edit_refresh();
2417
2418
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2419
}
2420
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2421

2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
/* 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;
}

2437
#ifndef DISABLE_BROWSER
2438
/* Our sort routine for file listings -- sort directories before
2439
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2440
2441
int diralphasort(const void *va, const void *vb)
{
2442
2443
2444
2445
    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
2446

2447
    if (aisdir != 0 && bisdir == 0)
2448
	return -1;
2449
    if (aisdir == 0 && bisdir != 0)
2450
	return 1;
2451

2452
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2453
2454
}

2455
/* Free our malloc()ed memory */
2456
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2457
{
2458
2459
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2460
2461
2462
    free(array);
}

2463
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2464
2465
void striponedir(char *foo)
{
2466
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2467

2468
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2469
    /* Don't strip the root dir */
2470
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2471
2472
	return;

2473
2474
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2475
    if (*tmp == '/')
2476
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2477
2478
2479
2480
2481

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

    if (tmp != foo)
2482
2483
2484
2485
2486
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2487
    }
Chris Allegretta's avatar
Chris Allegretta committed
2488
2489
}

2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
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;
}

2500
/* Initialize the browser code, including the list of files in *path */
2501
char **browser_init(const char *path, int *longest, int *numents)
2502
2503
2504
{
    DIR *dir;
    struct dirent *next;
2505
    char **filelist;
2506
    int i = 0;
2507
    size_t path_len;
2508
2509

    dir = opendir(path);
2510
    if (dir == NULL)
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
	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
2524
    filelist = (char **)nmalloc(*numents * sizeof (char *));
2525

2526
2527
2528
2529
    if (!strcmp(path, "/"))
	path = "";
    path_len = strlen(path);

2530
2531
2532
2533
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;

2534
2535
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2536
2537
	i++;
    }
2538
    closedir(dir);
2539
2540
2541
2542
2543
2544
2545

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

    return filelist;
}

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

2560
2561
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2562
2563
2564
    /* 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
2565
2566
2567
2568
2569
    if (path != NULL && strcmp(path, inpath)) {
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2570
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2571
    if (path == NULL)
2572
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2573
2574

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

2577
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2578
2579
2580
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2581
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2582
2583
2584
2585
2586
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2587
2588

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2589
    do {
2590
2591
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2592

2593
	check_statblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2594

2595
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2596
	currshortcut = browser_list;
2597
2598
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2599
2600
 	editline = 0;
	col = 0;
2601
	    
2602
	/* Compute line number we're on now, so we don't divide by zero later */
2603
2604
2605
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2606
2607

	switch (kbinput) {
2608

2609
#ifndef DISABLE_MOUSE
2610
	case KEY_MOUSE:
2611
	    if (getmouse(&mevent) == ERR)
2612
		return retval;
2613
2614
2615
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2616
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2617
2618
2619
2620
		int selectedbackup = selected;

		mevent.y -= 2;

2621
2622
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2623
		selected = (lineno / editwinrows) * editwinrows * width
2624
2625
2626
2627
2628
2629
			+ 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--;
2630
2631
2632

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2633
		if (selected > numents - 1)
2634
		    selected = numents - 1;
2635
		else if (selectedbackup == selected)
2636
2637
2638
2639
		    ungetch('s');	/* Unget the 'select' key */
	    } else	/* Must be clicking a shortcut */
		do_mouse();

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

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

2700
2701
2702
2703
2704
	    if (stat(filelist[selected], &st) == -1) {
		statusbar(_("Can't open \"%s\": %s"), filelist[selected], strerror(errno));
		beep();
		break;
	    }
2705

2706
2707
2708
2709
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2710
2711
	    }

2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
	    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
2722
		}
2723
2724
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2725

2726
2727
2728
2729
2730
	    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
2731
	    }
2732
2733
2734
2735
2736
2737
2738

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

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

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

2758
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2759

2760
2761
2762
	    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
2763
2764
	    }

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

	    /* Start over again with the new path value */
2781
2782
	    free_charptrarray(filelist, numents);
	    free(foo);
2783
2784
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2785
2786
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2787
	/* Stuff we want to abort the browser */
2788
	case NANO_CANCEL_KEY:
2789
	case NANO_EXIT_KEY:
2790
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2791
2792
	case 'E': /* Pico compatibility */
	case 'e':
2793
2794
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2795
2796
2797
2798
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2799
2800
	blank_edit();

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2845
	    /* Highlight the currently selected file/dir */
2846
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2847
		wattron(edit, A_REVERSE);
2848
2849
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2850
2851
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2852
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2853
2854
2855
2856
2857
	    waddstr(edit, "  ");
	    col += 2;

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

Chris Allegretta's avatar
Chris Allegretta committed
2873
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2874
2875
2876
2877
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2878

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

2889
2890
2891
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2892

2893
2894
2895
2896
2897
2898
2899
2900
    /*
     * 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);
2901
2902
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2903
	    path = getcwd(NULL, PATH_MAX + 1);
2904
	}
2905
    }
2906

2907
2908
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2909
    if (check_operating_dir(path, FALSE) != 0)
2910
2911
2912
2913
2914
2915
2916
2917
2918
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2919
    return bob;
2920
}
Chris Allegretta's avatar
Chris Allegretta committed
2921
#endif /* !DISABLE_BROWSER */
2922
2923
2924
2925
2926
2927

#ifndef NANO_SMALL
#ifdef ENABLE_NANORC
void load_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2928
    const struct passwd *userage = NULL;
2929
2930
    static char *nanohist;
    char *buf, *ptr;
Chris Allegretta's avatar
Chris Allegretta committed
2931
    char *homenv = getenv("HOME");
2932
2933
2934
    historyheadtype *history = &search_history;


Chris Allegretta's avatar
Chris Allegretta committed
2935
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2936
        nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
2937
2938
2939
2940
        sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2941
        nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
2942
        sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2943
2944
2945
2946
2947
    }

    /* assume do_rcfile has reported missing home dir */

    if (homenv != NULL || userage != NULL) {
2948
	hist = fopen(nanohist, "r");
2949
	if (hist == NULL) {
2950
2951
2952
            if (errno != ENOENT) {
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2953
		rcfile_error(_("Unable to open ~/.nano_history file: %s"), strerror(errno));
2954
	    }
2955
2956
2957
2958
2959
	    free(nanohist);
	} else {
	    buf = charalloc(1024);
	    while (fgets(buf, 1023, hist) != 0) {
		ptr = buf;
2960
		while (*ptr != '\n' && *ptr != '\0' && ptr < buf + 1023)
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
		    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
2980
    const struct passwd *userage = NULL;
2981
    char *nanohist = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2982
    char *homenv = getenv("HOME");
2983
2984
2985
2986
2987
2988
2989
    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
2990
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2991
	nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
2992
2993
2994
2995
	sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2996
	nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
2997
	sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2998
2999
3000
    }

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