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

22
23
#include "config.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

268
	num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
269
270
	totsize++;
	buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
271
    }
272
#ifndef NANO_SMALL
273
    else if (!ISSET(NO_CONVERT) && input == '\r') {
274
275
276
	/* If file conversion isn't disabled and the last character in
	   this file is a CR, read it in properly as a (Mac format)
	   line */
277
278
279
280
281
282
283
	buf[0] = input;
	buf[1] = '\0';
	len = 1;
	fileptr = read_line(buf, fileptr, &line1ins, len);
	num_lines++;
	totsize++;
	buf[0] = '\0';
284
285
    }
#endif
286

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

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

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

300
    /* Did we try to insert a file of 0 bytes? */
301
302
303
304
305
306
307
308
309
310
311
312
313
    if (num_lines != 0) {
	if (current != NULL) {
	    fileptr->next = current;
	    current->prev = fileptr;
	    renumber(current);
	    current_x = 0;
	    placewewant = 0;
	} else if (fileptr->next == NULL) {
	    filebot = fileptr;
	    new_magicline();
	    totsize--;
	    load_file(quiet);
	}
Chris Allegretta's avatar
Chris Allegretta committed
314
    }
315
316
317

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

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

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

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

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

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

    return 1;
}

395
/* This function will return the name of the first available extension
Chris Allegretta's avatar
Chris Allegretta committed
396
397
398
 * 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
399
char *get_next_filename(const char *name)
400
401
402
403
404
405
406
407
{
    int i = 0;
    char *buf = NULL;
    struct stat fs;

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

408
    while (TRUE) {
409
410

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

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

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

    return buf;
}

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

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

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

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

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

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

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

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

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

	    free(ans);

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

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

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

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

550
#ifndef NANO_SMALL
551
552
	if (i == NANO_EXTCMD_KEY) {
	    realname = mallocstrcpy(realname, "");
553
	    i = open_pipe(answer);
554
555
	} else {
#endif
556
	    realname = real_dir_from_tilde(answer);
557
	    i = open_file(realname, TRUE, loading_file);
558
#ifndef NANO_SMALL
559
	}
560
#endif
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

    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,
668
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
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->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
851
    do_gotopos(open_files->file_lineno, open_files->file_current_x,
	open_files->file_current_y, open_files->file_placewewant);
852

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

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

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

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

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

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

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

    }

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

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

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

    }

    load_open_file();

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

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

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

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

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

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

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

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

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

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

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

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

	}
    }

    load_open_file();

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

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

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

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

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

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

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

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

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

1014
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR) || !defined(NANO_SMALL)
1015
/*
1016
1017
1018
1019
1020
1021
 * When passed "[relative path]" or "[relative path][filename]" in
 * origpath, return "[full path]" or "[full path][filename]" on success,
 * or NULL on error.  This is still done if the file doesn't exist but
 * the relative path does (since the file could exist in memory but not
 * yet on disk); it is not done if the relative path doesn't exist (since
 * the first call to chdir() will fail then).
1022
 */
1023
char *get_full_path(const char *origpath)
1024
{
1025
1026
1027
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
1028
    char *expanded_origpath;
1029

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

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

1039
    if (d_here != NULL) {
1040
1041

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

	/* stat origpath; if stat() fails, assume that origpath refers to
	   a new file that hasn't been saved to disk yet (i. e. set
	   path_only to 0); if stat() succeeds, set path_only to 0 if
	   origpath doesn't refer to a directory, or to 1 if it does */
1051
	path_only = !stat(origpath, &fileinfo) && S_ISDIR(fileinfo.st_mode);
1052

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

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

1072
1073
1074
1075
1076
	/* search for the last slash in d_there */
	last_slash = strrchr(d_there, '/');

	/* if we didn't find one, copy d_here into d_there; all data is
	   then set up */
1077
	if (last_slash == NULL)
1078
	    d_there = mallocstrcpy(d_there, d_here);
1079
	else {
1080
1081
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1082
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1083
	    null_at(&d_there, last_slash_index + 1);
1084
1085
1086

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

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1097
1098
1099
		/* get the full pathname, and save it back in d_there,
		   tacking a slash on the end if we have a path but no
		   filename; if the saving fails, get out */
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109

		free(d_there);

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

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

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

	    /* finally, go back to where we were before, d_here (no error
	       checking is done on this chdir(), because we can do
	       nothing if it fails) */
	    chdir(d_here);
	}
	
	/* all data is set up; fill in newpath */

1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
	/* if we have a path and a filename, newpath = d_there +
	   d_there_file; otherwise, newpath = d_there */
	if (!path_only) {
	    newpath = charalloc(strlen(d_there) + strlen(d_there_file) + 1);
	    strcpy(newpath, d_there);
	    strcat(newpath, d_there_file);
	}
	else {
	    newpath = charalloc(strlen(d_there) + 1);
	    strcpy(newpath, d_there);
	}
1142
1143
1144
1145
1146
1147
1148
1149
1150

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

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

#ifndef DISABLE_SPELLER
/*
1155
1156
1157
 * This function accepts a path and returns the full path (via
 * get_full_path()).  On error, if the path doesn't reference a
 * directory, or if the directory isn't writable, it returns NULL.
1158
 */
1159
char *check_writable_directory(const char *path)
1160
{
1161
    char *full_path = get_full_path(path);
1162
    int writable;
1163
1164
    struct stat fileinfo;

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

    /* otherwise, stat() the full path to see if it's writable by the
       user; set writable to 1 if it is, or 0 if it isn't */
1171
    writable = !stat(full_path, &fileinfo) && (fileinfo.st_mode & S_IWUSR);
1172
1173

    /* if the full path doesn't end in a slash (meaning get_full_path()
1174
1175
1176
1177
       found that it isn't a directory) or isn't writable, free full_path
       and return NULL */
    if (full_path[strlen(full_path) - 1] != '/' || writable == 0) {
	free(full_path);
1178
	return NULL;
1179
    }
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189

    /* otherwise, return the full path */
    return full_path;
}

/*
 * This function accepts a directory name and filename prefix the same
 * way that tempnam() does, determines the location for its temporary
 * file the same way that tempnam() does, safely creates the temporary
 * file there via mkstemp(), and returns the name of the temporary file
1190
1191
1192
1193
1194
1195
1196
 * the same way that tempnam() does.  It does not reference the value of
 * TMP_MAX because the total number of random filenames that it can
 * generate using one prefix is equal to 256**6, which is a sufficiently
 * large number to handle most cases.  Since the behavior after tempnam()
 * generates TMP_MAX random filenames is implementation-defined, my
 * implementation is to go on generating random filenames regardless of
 * it.
1197
 */
1198
1199
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1200
1201
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1202
    int filedesc;
1203

1204
1205
      /* if $TMPDIR is set and non-empty, set tempdir to it, run it through
         get_full_path(), and save the result in full_tempdir; otherwise,
1206
         leave full_tempdir set to NULL */
1207
    TMPDIR_env = getenv("TMPDIR");
1208
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1209
	full_tempdir = check_writable_directory(TMPDIR_env);
1210

1211
1212
1213
    /* if $TMPDIR is blank or isn't set, or isn't a writable
       directory, and dirname isn't NULL, try it; otherwise, leave
       full_tempdir set to NULL */
1214
    if (full_tempdir == NULL && dirname != NULL)
1215
	full_tempdir = check_writable_directory(dirname);
1216
1217
1218

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

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

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

    /* like tempnam(), use only the first 5 characters of the prefix */
1231
1232
1233
1234
1235
1236
1237
1238
    strncat(full_tempdir, filename_prefix, 5);
    strcat(full_tempdir, "XXXXXX");
    filedesc = mkstemp(full_tempdir);

    /* if mkstemp succeeded, close the resulting file; afterwards, it'll be
       0 bytes long, so delete it; finally, return the filename (all that's
       left of it) */
    if (filedesc != -1) {
1239
	close(filedesc);
1240
1241
	unlink(full_tempdir);
	return full_tempdir;
1242
    }
1243
1244
1245

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

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

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

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

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

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

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

1289
    assert(full_operating_dir != NULL);
1290
1291

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

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

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

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

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

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

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

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

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

1425
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1426

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

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

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

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

1462
	FILE *backup_file;
1463
	char *backupname;
1464
	struct utimbuf filetime;
1465
	int copy_status;
1466

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    retval = 1;

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

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

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

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

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

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

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

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

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

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

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

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

1851
1852
1853
1854
1855
	/* 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,
1856
#ifndef NANO_SMALL
1857
		ans, NULL, "%s%s%s", _(msg), formatstr, backupstr
1858
#else
1859
		filename, "%s", _(msg)
1860
1861
1862
1863
1864
1865
#endif
		);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2003
    return dirtmp;
2004
2005
}

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

2016
    assert(dirptr != buf);
2017

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

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

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

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

2058
    strcat(buf, "*");
2059

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
2206
2207
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
2208
2209
char *input_tab(char *buf, int place, int *lastwastab, int *newplace,
	int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2210
2211
{
    /* Do TAB completion */
2212
    static int num_matches = 0, match_matches = 0;
2213
    static char **matches = (char **)NULL;
2214
    int pos = place, i = 0, col = 0, editline = 0;
2215
    int longestname = 0, is_dir = 0;
2216
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2217

2218
2219
    *list = 0;

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2223
	*lastwastab = 1;
2224

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

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

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

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

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

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

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

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

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

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

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

2296
	    if (is_dir != 0)
2297
		break;
2298
2299

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

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

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

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

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

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

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

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

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

2367
	    editline = 0;
2368

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

2377
	    foo = charalloc(longestname + 5);
2378

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

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

		strcat(foo, "  ");

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    dir = opendir(path);
2512
    if (dir == NULL)
2513
2514
2515
2516
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
2517
	if (strcmp(next->d_name, ".") == 0)
2518
2519
2520
2521
2522
2523
2524
2525
	   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
2526
    filelist = (char **)nmalloc(*numents * sizeof (char *));
2527

2528
    if (strcmp(path, "/") == 0)
2529
2530
2531
	path = "";
    path_len = strlen(path);

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

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

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

    return filelist;
}

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

2562
2563
    assert(inpath != NULL);

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

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

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

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

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

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

2595
	check_statblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2596

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

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

	switch (kbinput) {
2610

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

		mevent.y -= 2;

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

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

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

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

2704
	    if (stat(filelist[selected], &st) == -1) {
2705
2706
		statusbar(_("Can't open \"%s\": %s"), filelist[selected],
			strerror(errno));
2707
2708
2709
		beep();
		break;
	    }
2710

2711
2712
2713
2714
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2715
2716
	    }

2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
	    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
2727
		}
2728
2729
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2730

2731
2732
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
2733
2734
		statusbar(_("Can't open \"%s\": %s"), new_path,
			strerror(errno));
2735
2736
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2737
	    }
2738
2739
2740
2741
2742
2743
2744

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

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

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

2764
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2765

2766
2767
2768
	    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
2769
2770
	    }

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

	    /* Start over again with the new path value */
2787
2788
	    free_charptrarray(filelist, numents);
	    free(foo);
2789
2790
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2791
2792
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2793
	/* Stuff we want to abort the browser */
2794
	case NANO_CANCEL_KEY:
2795
	case NANO_EXIT_KEY:
2796
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2797
2798
	case 'E': /* Pico compatibility */
	case 'e':
2799
2800
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2801
2802
2803
2804
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2805
2806
	blank_edit();

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2851
	    /* Highlight the currently selected file/dir */
2852
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2853
		wattron(edit, A_REVERSE);
2854
2855
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2856
2857
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2858
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2859
2860
2861
2862
2863
	    waddstr(edit, "  ");
	    col += 2;

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

Chris Allegretta's avatar
Chris Allegretta committed
2879
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2880
2881
2882
2883
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2884

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

2895
2896
2897
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2898

2899
2900
2901
2902
2903
2904
2905
2906
    /*
     * 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);
2907
2908
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2909
	    path = getcwd(NULL, PATH_MAX + 1);
2910
	}
2911
    }
2912

2913
2914
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2915
    if (check_operating_dir(path, FALSE) != 0)
2916
2917
2918
2919
2920
2921
2922
2923
2924
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2925
    return bob;
2926
}
Chris Allegretta's avatar
Chris Allegretta committed
2927
#endif /* !DISABLE_BROWSER */
2928
2929
2930
2931
2932
2933

#ifndef NANO_SMALL
#ifdef ENABLE_NANORC
void load_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2934
    const struct passwd *userage = NULL;
2935
2936
    static char *nanohist;
    char *buf, *ptr;
Chris Allegretta's avatar
Chris Allegretta committed
2937
    char *homenv = getenv("HOME");
2938
2939
2940
    historyheadtype *history = &search_history;


Chris Allegretta's avatar
Chris Allegretta committed
2941
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2942
        nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
2943
2944
2945
2946
        sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2947
        nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
2948
        sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2949
2950
2951
2952
2953
    }

    /* assume do_rcfile has reported missing home dir */

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

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

Chris Allegretta's avatar
Chris Allegretta committed
2996
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2997
	nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
2998
2999
3000
3001
	sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3002
	nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
3003
	sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
3004
3005
3006
    }

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