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

22
23
#include "config.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return 1;
}

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

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

    return 1;
}

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

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

409
    while (TRUE) {
410
411

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

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

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

    return buf;
}

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

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

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

442
443
444
445
#if !defined(DISABLE_BROWSER) || !defined(NANO_SMALL) && defined(ENABLE_MULTIBUFFER)
  start_again:	/* Goto here when the user cancels the file browser. */
#endif

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

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

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

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

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

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

	    free(ans);

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

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

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

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

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

565
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
566
567
568
569
570
571
572
573
574
575
576
577
578
579
	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);
	}
580
581
#endif

582
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
583

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

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

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

598
#ifdef ENABLE_MULTIBUFFER
599
600
601
602
603
604
605
606
607
608
	/* If we've loaded another file, update the titlebar's contents */
	if (loading_file) {
	    clearok(topwin, FALSE);
	    titlebar(NULL);

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

Chris Allegretta's avatar
Chris Allegretta committed
609
610
611
612
613
614
615
616
617
618
619
#ifdef ENABLE_MULTIBUFFER
	if (!loading_file) {
#endif

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

#ifdef ENABLE_MULTIBUFFER
	}
#endif

620
	/* If we've gone off the bottom, recenter; otherwise, just redraw */
Chris Allegretta's avatar
Chris Allegretta committed
621
	if (current->lineno > editbot->lineno)
622
	    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
623
624
625
626
627
	else
	    edit_refresh();

    } else {
	statusbar(_("Cancelled"));
628
	i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
629
    }
630

Chris Allegretta's avatar
Chris Allegretta committed
631
632
633
634
#ifdef ENABLE_MULTIBUFFER
  skip_insert:
#endif

635
636
637
638
639
    free(inspath);
    inspath = NULL;

    display_main_list();
    return i;
Chris Allegretta's avatar
Chris Allegretta committed
640
641
}

642
643
644
int do_insertfile_void(void)
{
    int result = 0;
645
#ifdef ENABLE_MULTIBUFFER
646
647
648
649
650
651
652
653
    if (ISSET(VIEW_MODE)) {
	if (ISSET(MULTIBUFFER))
	    result = do_insertfile(1);
	else
	    statusbar(_("Key illegal in non-multibuffer mode"));
    }
    else
	result = do_insertfile(ISSET(MULTIBUFFER));
654
655
656
657
658
659
660
661
#else
    result = do_insertfile(0);
#endif

    display_main_list();
    return result;
}

662
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
663
664
665
/* Create a new openfilestruct node. */
openfilestruct *make_new_opennode(openfilestruct *prevnode)
{
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
666
    openfilestruct *newnode = (openfilestruct *)nmalloc(sizeof(openfilestruct));
Chris Allegretta's avatar
Chris Allegretta committed
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721

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

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

    return newnode;
}

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

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

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

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

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

/* Deallocate all memory associated with this and later files,
 * including the lines of text. */
void free_openfilestruct(openfilestruct *src)
{
    if (src != NULL) {
	while (src->next != NULL) {
	    src = src->next;
	    delete_opennode(src->prev);
#ifdef DEBUG
722
	    fprintf(stderr, "%s: free'd a node, YAY!\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
723
724
725
726
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
727
	fprintf(stderr, "%s: free'd last node.\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
728
729
730
731
#endif
    }
}

732
/*
733
 * Add/update an entry to the open_files openfilestruct.  If update is
734
 * zero, a new entry is created; otherwise, the current entry is updated.
735
 * Return 0 on success or 1 on error.
736
 */
737
int add_open_file(int update)
738
{
739
    openfilestruct *tmp;
740

741
    if (fileage == NULL || current == NULL || filename == NULL)
742
743
744
	return 1;

    /* if no entries, make the first one */
745
    if (open_files == NULL)
746
	open_files = make_new_opennode(NULL);
747
748
749
750
751
752
753

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

757
758
	tmp = make_new_opennode(NULL);
	splice_opennode(open_files, tmp, open_files->next);
759
760
761
762
	open_files = open_files->next;
    }

    /* save current filename */
763
    open_files->filename = mallocstrcpy(open_files->filename, filename);
764

765
766
767
768
769
#ifndef NANO_SMALL
    /* save the file's stat */
    open_files->originalfilestat = originalfilestat;
#endif

770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
    /* 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 */
786
    open_files->file_lineno = current->lineno;
787

788
789
790
791
    /* start with default modification status: unmodified (and marking
       status, if available: unmarked) */
    open_files->file_flags = 0;

Chris Allegretta's avatar
Chris Allegretta committed
792
793
794
795
    /* if we're updating, save current modification status (and marking
       status, if available) */
    if (update) {
#ifndef NANO_SMALL
796
797
	if (ISSET(MODIFIED))
	    open_files->file_flags |= MODIFIED;
Chris Allegretta's avatar
Chris Allegretta committed
798
799
800
	if (ISSET(MARK_ISSET)) {
	    open_files->file_mark_beginbuf = mark_beginbuf;
	    open_files->file_mark_beginx = mark_beginx;
801
	    open_files->file_flags |= MARK_ISSET;
Chris Allegretta's avatar
Chris Allegretta committed
802
803
	}
#else
804
805
	if (ISSET(MODIFIED))
	    open_files->file_flags |= MODIFIED;
Chris Allegretta's avatar
Chris Allegretta committed
806
807
808
#endif
    }

809
810
811
    /* 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
812
813
814
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
815
    }
816
817

#ifdef DEBUG
818
    fprintf(stderr, "filename is %s\n", open_files->filename);
819
820
821
822
823
824
825
826
827
828
829
830
#endif

    return 0;
}

/*
 * Read the current entry in the open_files structure and set up the
 * currently open file using that entry's information.  Return 0 on
 * success or 1 on error.
 */
int load_open_file(void)
{
831
    if (open_files == NULL)
832
833
834
835
	return 1;

    /* set up the filename, the file buffer, the total number of lines in
       the file, and the total file size */
836
    filename = mallocstrcpy(filename, open_files->filename);
837
838
839
#ifndef NANO_SMALL
    originalfilestat = open_files->originalfilestat;
#endif
840
    fileage = open_files->fileage;
841
    current = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
842
    filebot = open_files->filebot;
843
844
845
    totlines = open_files->file_totlines;
    totsize = open_files->file_totsize;

Chris Allegretta's avatar
Chris Allegretta committed
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
    /* 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
861

Chris Allegretta's avatar
Chris Allegretta committed
862
863
864
865
#ifdef ENABLE_COLOR
    update_color();
#endif

866
867
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
868
    do_gotopos(open_files->file_lineno, open_files->file_current_x, open_files->file_current_y, open_files->file_placewewant);
869

Chris Allegretta's avatar
Chris Allegretta committed
870
    /* update the titlebar */
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
    clearok(topwin, FALSE);
    titlebar(NULL);

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

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

    /* if we're not about to close the current entry, update it before
890
       doing anything */
891
    if (!closing_file)
892
	add_open_file(1);
893

894
    if (open_files->prev == NULL && open_files->next == NULL) {
895
896
897

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
898
	    statusbar(_("No more open file buffers"));
899
900
901
	return 1;
    }

902
    if (open_files->prev != NULL) {
903
904
905
	open_files = open_files->prev;

#ifdef DEBUG
906
	fprintf(stderr, "filename is %s\n", open_files->filename);
907
908
909
910
#endif

    }

911
    else if (open_files->next != NULL) {
912
913

	/* if we're at the beginning, wrap around to the end */
914
	while (open_files->next != NULL)
915
916
917
	    open_files = open_files->next;

#ifdef DEBUG
918
	    fprintf(stderr, "filename is %s\n", open_files->filename);
919
920
921
922
923
924
#endif

    }

    load_open_file();

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

928
929
930
931
932
933
934
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

935
936
937
/* This function is used by the shortcut list. */
int open_prevfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
938
    return open_prevfile(0);
939
940
}

941
942
943
944
945
946
947
948
/*
 * Open the next entry in the open_files structure.  If closing_file is
 * zero, update the current entry before switching from it.  Otherwise, we
 * are about to close that entry, so don't bother doing so.  Return 0 on
 * success and 1 on error.
 */
int open_nextfile(int closing_file)
{
949
    if (open_files == NULL)
950
951
952
	return 1;

    /* if we're not about to close the current entry, update it before
953
       doing anything */
954
    if (!closing_file)
955
	add_open_file(1);
956

957
    if (open_files->prev == NULL && open_files->next == NULL) {
958
959
960

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
961
	    statusbar(_("No more open file buffers"));
962
963
964
	return 1;
    }

965
    if (open_files->next != NULL) {
966
967
968
	open_files = open_files->next;

#ifdef DEBUG
969
	fprintf(stderr, "filename is %s\n", open_files->filename);
970
971
972
#endif

    }
973
    else if (open_files->prev != NULL) {
974
975

	/* if we're at the end, wrap around to the beginning */
976
	while (open_files->prev != NULL) {
977
978
979
	    open_files = open_files->prev;

#ifdef DEBUG
980
	    fprintf(stderr, "filename is %s\n", open_files->filename);
981
982
983
984
985
986
987
#endif

	}
    }

    load_open_file();

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

991
992
993
994
995
996
997
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

998
999
1000
/* This function is used by the shortcut list. */
int open_nextfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1001
    return open_nextfile(0);
1002
1003
}

1004
1005
/*
 * Delete an entry from the open_files filestruct.  After deletion of an
1006
 * entry, the next or previous entry is opened, whichever is found first.
1007
1008
1009
1010
 * Return 0 on success or 1 on error.
 */
int close_open_file(void)
{
1011
    openfilestruct *tmp;
1012

1013
    if (open_files == NULL)
1014
1015
	return 1;

Chris Allegretta's avatar
Chris Allegretta committed
1016
1017
1018
    /* 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 */
1019
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
1020
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1021

1022
    tmp = open_files;
1023
1024
    if (open_nextfile(1)) {
	if (open_prevfile(1))
1025
1026
1027
	    return 1;
    }

1028
1029
    unlink_opennode(tmp);
    delete_opennode(tmp);
1030
1031
1032
1033
1034

    shortcut_init(0);
    display_main_list();
    return 0;
}
1035
#endif /* MULTIBUFFER */
1036

1037
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR) || !defined(NANO_SMALL)
1038
/*
1039
1040
1041
1042
1043
1044
 * 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).
1045
 */
1046
char *get_full_path(const char *origpath)
1047
{
1048
1049
1050
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
1051
    char *expanded_origpath;
1052

1053
    /* first, get the current directory, and tack a slash onto the end of
1054
       it, unless it turns out to be "/", in which case leave it alone */
1055
1056
1057
1058
1059
1060
1061

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

1062
    if (d_here != NULL) {
1063
1064

	align(&d_here);
1065
	if (strcmp(d_here, "/")) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1066
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1067
1068
	    strcat(d_here, "/");
	}
1069
1070
1071
1072
1073

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

1076
	expanded_origpath = real_dir_from_tilde(origpath);
1077
	/* save the value of origpath in both d_there and d_there_file */
1078
1079
1080
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1081

1082
1083
1084
1085
1086
1087
	/* 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
1088
		d_there = charealloc(d_there, strlen(d_there) + 2);
1089
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1090
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1091
1092
1093
1094
		strcat(d_there_file, "/");
	    }
	}

1095
1096
1097
1098
1099
	/* 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 */
1100
	if (last_slash == NULL)
1101
	    d_there = mallocstrcpy(d_there, d_here);
1102
	else {
1103
1104
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1105
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1106
	    null_at(&d_there, last_slash_index + 1);
1107
1108
1109

	    /* and remove all non-file elements from d_there_file (i. e.
	       everything before and including the last slash); if we
1110
	       have a path but no filename, don't do anything */
1111
1112
1113
1114
1115
1116
	    if (!path_only) {
		last_slash = strrchr(d_there_file, '/');
		last_slash++;
		strcpy(d_there_file, last_slash);
		align(&d_there_file);
	    }
1117
1118
1119

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1120
1121
1122
		/* 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 */
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132

		free(d_there);

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

		align(&d_there);
1133
		if (d_there != NULL) {
1134
1135
1136
1137

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
		    if (strcmp(d_there, "/")) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1138
			d_there = charealloc(d_there, strlen(d_there) + 2);
1139
1140
			strcat(d_there, "/");
		    }
1141
1142
1143
		}
		else
		    return NULL;
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
	    }

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

1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
	/* 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);
	}
1165
1166
1167
1168
1169
1170
1171
1172
1173

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

    return newpath;
}
1174
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1175
1176
1177

#ifndef DISABLE_SPELLER
/*
1178
1179
1180
 * 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.
1181
 */
1182
char *check_writable_directory(const char *path)
1183
{
1184
    char *full_path = get_full_path(path);
1185
    int writable;
1186
1187
    struct stat fileinfo;

1188
    /* if get_full_path() failed, return NULL */
1189
    if (full_path == NULL)
1190
	return NULL;
1191
1192
1193

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

    /* if the full path doesn't end in a slash (meaning get_full_path()
1197
1198
1199
1200
       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);
1201
	return NULL;
1202
    }
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212

    /* 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
1213
1214
1215
1216
1217
1218
1219
 * 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.
1220
 */
1221
1222
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1223
1224
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1225
    int filedesc;
1226

1227
1228
      /* 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,
1229
         leave full_tempdir set to NULL */
1230
    TMPDIR_env = getenv("TMPDIR");
1231
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1232
	full_tempdir = check_writable_directory(TMPDIR_env);
1233

1234
1235
1236
    /* 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 */
1237
    if (full_tempdir == NULL && dirname != NULL)
1238
	full_tempdir = check_writable_directory(dirname);
1239
1240
1241

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1242
    if (full_tempdir == NULL)
1243
	full_tempdir = check_writable_directory(P_tmpdir);
1244
1245

    /* if P_tmpdir didn't work, use /tmp instead */
1246
    if (full_tempdir == NULL) {
1247
1248
1249
1250
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1251
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1252
1253

    /* like tempnam(), use only the first 5 characters of the prefix */
1254
1255
1256
1257
1258
1259
1260
1261
    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) {
1262
	close(filedesc);
1263
1264
	unlink(full_tempdir);
	return full_tempdir;
1265
    }
1266
1267
1268

    free(full_tempdir);
    return NULL;
1269
1270
}
#endif /* !DISABLE_SPELLER */
1271
1272

#ifndef DISABLE_OPERATINGDIR
1273
1274
1275
1276
1277
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1278
    if (operating_dir == NULL)
1279
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1280

1281
1282
1283
    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
1284
     * inaccessible, unset operating_dir. */
1285
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1286
1287
1288
1289
1290
1291
1292
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1293
/* Check to see if we're inside the operating directory.  Return 0 if we
1294
1295
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1296
 * completion will work. */
1297
int check_operating_dir(const char *currpath, int allow_tabcomp)
1298
{
1299
1300
1301
1302
    /* 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. */
1303

1304
1305
1306
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1307

1308
    /* If no operating directory is set, don't bother doing anything. */
1309
    if (operating_dir == NULL)
1310
	return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1311

1312
    assert(full_operating_dir != NULL);
1313
1314

    fullpath = get_full_path(currpath);
1315
1316
1317
1318
1319
1320
1321
1322

    /* 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. */
1323
    if (fullpath == NULL)
1324
	return allow_tabcomp;
1325
1326
1327
1328
1329

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

1330
1331
1332
1333
1334
    /* 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. */
1335
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1336
1337
	retval = 1;
    free(fullpath);	
1338
1339

    /* Otherwise, we're still inside it. */
1340
    return retval;
1341
}
1342
1343
#endif

1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
#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

1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
/* Read from inn, write to out.  We assume inn is opened for reading,
 * and out for writing.  We return 0 on success, -1 on read error, -2 on
 * write error. */
int copy_file(FILE *inn, FILE *out)
{
    char buf[BUFSIZ];
    size_t charsread;
    int retval = 0;

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

/* Write a file out.  If tmp is nonzero, we set the umask to disallow
 * anyone else from accessing the file, we don't set the global variable
 * filename to its name, and we don't print out how many lines we wrote
 * on the statusbar.
Chris Allegretta's avatar
Chris Allegretta committed
1400
 *
1401
1402
 * 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.
1403
 *
1404
1405
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1406
1407
 *
 * nonamechange means don't change the current filename, it is ignored
1408
 * if tmp is nonzero or if we're appending/prepending.
1409
1410
 *
 * Return -1 on error, 1 on success. */
1411
int write_file(const char *name, int tmp, int append, int nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1412
{
1413
1414
    int retval = -1;
	/* Instead of returning in this function, you should always
1415
1416
1417
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
    const filestruct *fileptr = fileage;
1418
    int fd;
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
    mode_t original_umask = 0;
	/* Our umask, from when nano started. */
    int realexists;
	/* The result of stat().  True if the file exists, false
	 * otherwise.  If name is a link that points nowhere, realexists
	 * is false. */
    struct stat st;
	/* The status fields filled in by stat(). */
    int anyexists;
	/* Result of lstat().  Same as realexists unless name is a
	 * link. */
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
	/* name after ~ expansion. */
    FILE *f;
	/* The actual file, realname, we are writing to. */
    char *tempname = NULL;
	/* The temp file name we write to on prepend. */
Chris Allegretta's avatar
Chris Allegretta committed
1438

1439
    assert(name != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1440
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1441
1442
1443
	statusbar(_("Cancelled"));
	return -1;
    }
1444
1445
    if (!tmp)
	titlebar(NULL);
1446

1447
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1448

1449
#ifndef DISABLE_OPERATINGDIR
1450
    /* If we're writing a temporary file, we're probably going outside
1451
1452
     * the operating directory, so skip the operating directory test. */
    if (!tmp && check_operating_dir(realname, 0) != 0) {
1453
1454
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1455
1456
1457
    }
#endif

1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
    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)) {
	statusbar(_("Cannot prepend or append to a symlink with --nofollow set."));
	goto cleanup_and_exit;
    }

1469
    /* Save the state of file at the end of the symlink (if there is
1470
1471
     * one). */
    realexists = stat(realname, &st) != -1;
1472

1473
1474
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1475
1476
1477
1478
1479
1480
1481
1482
     * 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)) {

1483
	FILE *backup_file;
1484
	char *backupname;
1485
	struct utimbuf filetime;
1486
	int copy_status;
1487

1488
	/* Save the original file's access and modification times. */
1489
1490
1491
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1492
	/* Open the original file to copy to the backup. */
1493
	f = fopen(realname, "rb");
1494
	if (f == NULL) {
1495
	    statusbar(_("Error reading %s: %s"), realname,
1496
		strerror(errno));
1497
	    goto cleanup_and_exit;
1498
1499
	}

1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
	/* 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);
	}
1532

1533
1534
1535
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1536
	backup_file = fopen(backupname, "wb");
1537
1538
1539
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
	    statusbar(_("Error writing %s: %s"), backupname, strerror(errno));
1540
	    free(backupname);
1541
1542
1543
1544
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1545
1546
1547
	}

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

1551
1552
1553
1554
1555
1556
1557
1558
1559
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
	/* And set metadata. */
	if (copy_status != 0 || chown(backupname, originalfilestat.st_uid,
		originalfilestat.st_gid) == -1 ||
		utime(backupname, &filetime) == -1) {
	    free(backupname);
	    if (copy_status == -1)
		statusbar(_("Error reading %s: %s"), realname,
1560
			strerror(errno));
1561
1562
1563
1564
1565
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1566
1567
	free(backupname);
    }
1568
#endif /* !NANO_SMALL */
1569

1570
1571
1572
1573
1574
1575
    /* 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));
1576
	goto cleanup_and_exit;
1577
    }
1578

1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
    original_umask = umask(0);
    umask(original_umask);
    /* If we create a temp file, we don't let anyone else access it.  We
     * create a temp file if tmp is nonzero or if we prepend. */
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

    /* If we are prepending, copy the file to a temp file. */
    if (append == 2) {
	int fd_source;
	FILE *f_source = NULL;

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

1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
	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);
1623
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1624
1625
1626
	}
    }

1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
    /* Now open the file in place.  Use O_EXCL if tmp is nonzero.  This
     * is now copied from joe, because wiggy says so *shrug*. */
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
	S_IRUSR | S_IWUSR);

    /* Put the umask back to the user's original value. */
    umask(original_umask);

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1643
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1644
    if (f == NULL) {
1645
1646
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1647
	goto cleanup_and_exit;
1648
1649
    }

1650
1651
1652
1653
1654
1655
    /* There might not be a magic line.  There won't be when writing out
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1656

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

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

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

1665
	if (size < data_len) {
1666
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1667
	    fclose(f);
1668
	    goto cleanup_and_exit;
1669
	}
1670
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1671
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1672
1673
1674
1675
1676
	    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
1677
1678

	if (!ISSET(MAC_FILE))
1679
#endif
1680
1681
1682
1683
1684
	    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
1685
1686
1687
1688
1689

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

1690
    /* If we're prepending, open the temp file, and append it to f. */
1691
    if (append == 2) {
1692
1693
1694
1695
1696
1697
1698
1699
	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);
1700
	}
1701
	if (f_source == NULL) {
1702
1703
	    statusbar(_("Error reading %s: %s"), tempname, strerror(errno));
	    fclose(f);
1704
	    goto cleanup_and_exit;
1705
1706
	}

1707
1708
1709
	if (copy_file(f_source, f) == -1
		|| unlink(tempname) == -1) {
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1710
	    goto cleanup_and_exit;
1711
	}
1712
1713
1714
1715
    } 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
1716
    }
1717

1718
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1719
	if (!nonamechange) {
1720
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1721
1722
#ifdef ENABLE_COLOR
	    update_color();
1723
1724
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1725
1726
#endif
	}
1727

1728
1729
1730
1731
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1732
1733
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1734
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1735
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1736
    }
1737
1738
1739
1740
1741

    retval = 1;

  cleanup_and_exit:
    free(realname);
1742
    free(tempname);
1743
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
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
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
#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
 * write_file() with the values of name, temp, append, and nonamechange.
 * Finally, set fileage and filebot back to their old values and
 * return. */
int write_marked(const char *name, int tmp, int append, int
	nonamechange)
{
    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;

    retval = write_file(name, tmp, append, nonamechange);

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

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

1809
int do_writeout(int exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1810
{
1811
1812
    int i;
    int append = 0;
1813
#ifdef NANO_EXTRA
1814
    static int did_cred = FALSE;
1815
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1816

1817
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1818
    currshortcut = writefile_list;
1819
1820
#endif

1821
    if (exiting && ISSET(TEMP_OPT)) {
1822
	i = -1;
1823
	if (filename[0] != '\0') {
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
	    i = write_file(filename, FALSE, 0, FALSE);
	    if (i == 1) {
		/* Write succeeded. */
		display_main_list();
		return 1;
	    }
	}

	/* No filename or the write above failed. */
	if (i == -1) {
Chris Allegretta's avatar
Chris Allegretta committed
1834
1835
1836
	    UNSET(TEMP_OPT);
	    do_exit();

1837
	    /* They cancelled; abort quit. */
Chris Allegretta's avatar
Chris Allegretta committed
1838
	    return -1;
1839
	}
Chris Allegretta's avatar
Chris Allegretta committed
1840
1841
    }

1842
#ifndef NANO_SMALL
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
    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);
1853
1854
	const char *formatstr, *backupstr;

1855
1856
1857
1858
1859
1860
1861
	if (ISSET(MAC_FILE))
	   formatstr = _(" [Mac Format]");
	else if (ISSET(DOS_FILE))
	   formatstr = _(" [DOS Format]");
	else
	   formatstr = "";

1862
1863
1864
1865
1866
	if (ISSET(BACKUP_FILE))
	   backupstr = _(" [Backup]");
	else
	   backupstr = "";

1867
	/* Be nice to the translation folks. */
1868
	if (ISSET(MARK_ISSET) && !exiting) {
1869
	    if (append == 2)
1870
		msg = _("Prepend Selection to File");
Chris Allegretta's avatar
Chris Allegretta committed
1871
	    else if (append == 1)
1872
		msg = _("Append Selection to File");
1873
	    else
1874
1875
1876
		msg = _("Write Selection to File");
	} else
#endif /* !NANO_SMALL */
1877
	if (append == 2)
1878
	    msg = _("File Name to Prepend to");
Chris Allegretta's avatar
Chris Allegretta committed
1879
	else if (append == 1)
1880
	    msg = _("File Name to Append to");
1881
	else
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
	    msg = _("File Name to Write");

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

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

1896
1897
1898
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
1899
	    return -1;
1900
	}
Chris Allegretta's avatar
Chris Allegretta committed
1901

1902
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1903
	if (i == NANO_TOFILES_KEY) {
1904
	    char *tmp = do_browse_from(answer);
1905

1906
	    currshortcut = writefile_list;
1907
1908
1909
1910
	    if (tmp == NULL)
		continue;
	    free(answer);
	    answer = tmp;
1911
	} else
1912
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1913
#ifndef NANO_SMALL
1914
1915
1916
	if (i == TOGGLE_DOS_KEY) {
	    UNSET(MAC_FILE);
	    TOGGLE(DOS_FILE);
1917
	    continue;
1918
1919
1920
	} else if (i == TOGGLE_MAC_KEY) {
	    UNSET(DOS_FILE);
	    TOGGLE(MAC_FILE);
1921
	    continue;
1922
1923
	} else if (i == TOGGLE_BACKUP_KEY) {
	    TOGGLE(BACKUP_FILE);
1924
1925
	    continue;
	} else
1926
#endif /* !NANO_SMALL */
1927
1928
1929
1930
1931
1932
1933
	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
1934

Chris Allegretta's avatar
Chris Allegretta committed
1935
#ifdef DEBUG
1936
	fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1937
#endif
1938
1939

#ifdef NANO_EXTRA
1940
	if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy")
1941
		&& !did_cred) {
1942
	    do_credits();
1943
	    did_cred = TRUE;
1944
1945
	    return -1;
	}
1946
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1947
	if (append == 0 && strcmp(answer, filename)) {
1948
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1949

1950
	    if (!stat(answer, &st)) {
1951
		i = do_yesno(FALSE, _("File exists, OVERWRITE ?"));
1952
1953
1954
1955
1956
1957
		if (i == 0 || i == -1)
		    continue;
	    } else if (filename[0] != '\0'
#ifndef NANO_SMALL
		&& (!ISSET(MARK_ISSET) || exiting)
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1958
		) {
1959
		i = do_yesno(FALSE, _("Save file under DIFFERENT NAME ?"));
1960
1961
1962
		if (i == 0 || i == -1)
		    continue;
	    }
1963
	}
Chris Allegretta's avatar
Chris Allegretta committed
1964

1965
#ifndef NANO_SMALL
1966
1967
	/* Here's where we allow the selected text to be written to
	 * a separate file. */
1968
	if (ISSET(MARK_ISSET) && !exiting)
1969
	    i = write_marked(answer, FALSE, append, FALSE);
1970
	else
1971
#endif /* !NANO_SMALL */
1972
	    i = write_file(answer, FALSE, append, FALSE);
1973

1974
#ifdef ENABLE_MULTIBUFFER
1975
	/* If we're not about to exit, update the current entry in
1976
	 * the open_files structure. */
1977
1978
	if (!exiting)
	    add_open_file(1);
1979
#endif
1980
1981
	display_main_list();
	return i;
1982
    } /* while (TRUE) */
Chris Allegretta's avatar
Chris Allegretta committed
1983
1984
1985
1986
}

int do_writeout_void(void)
{
1987
    return do_writeout(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
1988
}
Chris Allegretta's avatar
Chris Allegretta committed
1989

1990
/* Return a malloc()ed string containing the actual directory, used
1991
1992
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1993
{
Chris Allegretta's avatar
Chris Allegretta committed
1994
    char *dirtmp = NULL;
1995

1996
    if (buf[0] == '~') {
1997
1998
1999
2000
2001
2002
2003
	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++)
	    ;

2004
2005
	/* Determine home directory using getpwuid() or getpwent(),
	   don't rely on $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
2006
2007
2008
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
2009
2010
2011
2012
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
			strncmp(userdata->pw_name, buf + 1, i - 1));
Chris Allegretta's avatar
Chris Allegretta committed
2013
2014
	}
	endpwent();
2015

2016
	if (userdata != NULL) {	/* User found */
2017
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2018
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
2019
	}
2020
    }
2021

2022
2023
2024
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

2025
    return dirtmp;
2026
2027
}

Chris Allegretta's avatar
Chris Allegretta committed
2028
#ifndef DISABLE_TABCOMP
2029
2030
2031
/* 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
2032
int append_slash_if_dir(char *buf, int *lastwastab, int *place)
2033
{
2034
    char *dirptr = real_dir_from_tilde(buf);
2035
    struct stat fileinfo;
2036
    int ret = 0;
2037

2038
    assert(dirptr != buf);
2039

2040
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
2041
	strncat(buf, "/", 1);
2042
	(*place)++;
2043
	/* now we start over again with # of tabs so far */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2044
	*lastwastab = 0;
2045
	ret = 1;
2046
2047
    }

2048
    free(dirptr);
2049
    return ret;
2050
}
Chris Allegretta's avatar
Chris Allegretta committed
2051
2052

/*
Chris Allegretta's avatar
Chris Allegretta committed
2053
2054
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
2055
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
 *
 * 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)
{
2073
    char **matches = (char **)NULL;
2074
    char *matchline = NULL;
2075
    struct passwd *userdata;
2076

2077
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2078
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2079

2080
    strcat(buf, "*");
2081

2082
    while ((userdata = getpwent()) != NULL) {
2083

2084
	if (check_wildcard_match(userdata->pw_name, &buf[1]) == TRUE) {
2085
2086
2087
2088

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

2090
2091
2092
2093
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2094
2095
	    if (operating_dir != NULL) {
		if (check_operating_dir(userdata->pw_dir, 1) != 0)
2096
2097
2098
2099
		    continue;
	    }
#endif

2100
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2101
2102
2103
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
	    ++*num_matches;
2104

2105
2106
2107
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2108
	}
2109
2110
    }
    endpwent();
2111

2112
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2113
2114
2115
2116
2117
2118
2119
}

/* 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
2120
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2121
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2122
2123
2124
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2125
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2126
2127
2128
2129

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

2130
    /* Okie, if there's a / in the buffer, strip out the directory part */
2131
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2132
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2133
2134
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2135
	    tmp--;
2136

Chris Allegretta's avatar
Chris Allegretta committed
2137
2138
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2139
2140
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2141

Chris Allegretta's avatar
Chris Allegretta committed
2142
    } else {
2143
2144

#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2145
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
2146
#else
2147
	/* The better, but apparently segfault-causing way */
Chris Allegretta's avatar
Chris Allegretta committed
2148
	if ((dirname = getcwd(NULL, 0)) == NULL)
2149
#endif /* PATH_MAX */
Chris Allegretta's avatar
Chris Allegretta committed
2150
2151
2152
2153
2154
2155
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2156
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2157
2158
2159
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2160

Chris Allegretta's avatar
Chris Allegretta committed
2161
2162
2163
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2164
2165

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2166
    fprintf(stderr, "\nDir = %s\n", dirname);
2167
2168
2169
2170
2171
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2172
    dir = opendir(dirname);
2173
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2174
2175
2176
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2177
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2178
2179
2180
2181
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2182
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2183
2184
2185
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
2186
2187
2188
2189

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2190
2191
2192
2193
2194
2195
2196
2197

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

2198
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2199
2200
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2201
2202
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2203
		if (check_operating_dir(tmp2, 1) != 0) {
2204
2205
2206
2207
2208
2209
2210
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2211
	    tmp2 = NULL;
2212
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2213
2214
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2215
	    ++*num_matches;
2216
2217
2218
2219

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2220
2221
	}
    }
2222
2223
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2224

2225
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2226
2227
}

Chris Allegretta's avatar
Chris Allegretta committed
2228
2229
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2230
char *input_tab(char *buf, int place, int *lastwastab, int *newplace, int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2231
2232
{
    /* Do TAB completion */
2233
    static int num_matches = 0, match_matches = 0;
2234
    static char **matches = (char **)NULL;
2235
    int pos = place, i = 0, col = 0, editline = 0;
2236
    int longestname = 0, is_dir = 0;
2237
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2238

2239
2240
    *list = 0;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2241
    if (*lastwastab == FALSE) {
2242
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2243

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2244
	*lastwastab = 1;
2245

Chris Allegretta's avatar
Chris Allegretta committed
2246
2247
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2248
2249
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2250

2251
2252
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2253
2254

	/* skip any leading white space */
2255
	while (*tmp && isspace((int)*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2256
2257
2258
2259
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2260
2261
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2262
	    free(matches);
2263
	    matches = (char **)NULL;
2264
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2265
2266
2267
2268
2269
	}

	/* 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
2270
2271
2272
2273
2274
	/* 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. */
2275
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2276
	    buf = mallocstrcpy(buf, tmp);
2277
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2278
	}
2279
2280
2281
2282
2283
2284
	/* 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
2285
2286
2287

	/* Try to match everything in the current working directory that
	 * matches.  */
2288
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2289
2290
2291
	    matches = cwd_tab_completion(tmp, &num_matches);

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

2294
2295
2296
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2297
	/* Did we find exactly one match? */
2298
	switch (num_matches) {
2299
	case 0:
2300
	    blank_edit();
2301
	    wrefresh(edit);
2302
2303
	    break;
	case 1:
2304

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

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

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

2317
	    if (is_dir != 0)
2318
		break;
2319
2320

	    copyto = tmp;
2321
2322
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2323
2324
		tmp++;

2325
	    /* write out the matched name */
2326
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2327
2328
	    *newplace += strlen(matches[0]) - pos;

2329
2330
2331
2332
2333
2334
2335
	    /* 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;

2336
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2337
	    append_slash_if_dir(buf, lastwastab, newplace);
2338

2339
2340
	    break;
	default:
2341
	    /* Check to see if all matches share a beginning, and, if so,
2342
	       tack it onto buf and then beep */
2343

2344
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2345
2346
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2347
		tmp++;
2348
	    } else
2349
2350
		tmp = buf;

2351
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2352
		 pos <= strlen(matches[0]); pos++)
2353
2354
		tmp++;

2355
	    while (TRUE) {
2356
2357
2358
2359
2360
2361
2362
2363
		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++;
		}
2364
2365
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2366
		    /* All the matches have the same character at pos+1,
2367
		       so paste it into buf... */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2368
		    buf = charealloc(buf, strlen(buf) + 2);
2369
		    strncat(buf, matches[0] + pos, 1);
2370
		    *newplace += 1;
2371
		    pos++;
2372
		} else {
2373
2374
2375
2376
2377
		    beep();
		    break;
		}
	    }
	}
Chris Allegretta's avatar
Chris Allegretta committed
2378
2379
2380
2381
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2382
	if (matches != NULL && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2383
2384
2385
2386
2387

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

2388
	    editline = 0;
2389

2390
2391
2392
2393
2394
2395
2396
2397
	    /* 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;

2398
	    foo = charalloc(longestname + 5);
2399

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

2403
		/* make each filename shown be the same length as the longest
2404
		   filename, with two spaces at the end */
2405
2406
2407
2408
2409
2410
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2411
2412
		/* Disable el cursor */
		curs_set(0);
2413
2414
2415
2416
2417
2418
		/* 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 */
2419
		if (col > COLS - longestname && i + 1 < num_matches) {
2420
2421
		    editline++;
		    wmove(edit, editline, 0);
2422
2423
2424
2425
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2426
2427
2428
		    col = 0;
		}
	    }
2429
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2430
	    wrefresh(edit);
2431
	    *list = 1;
2432
2433
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2434
2435
    }

2436
2437
2438
2439
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
    if (*list == 0)
	edit_refresh();
2440
2441
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2442
}
2443
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2444

2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
#if !defined(DISABLE_BROWSER) || !defined(NANO_SMALL)
/* 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;
}
#endif

2462
#ifndef DISABLE_BROWSER
2463
/* Our sort routine for file listings -- sort directories before
2464
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2465
2466
int diralphasort(const void *va, const void *vb)
{
2467
2468
2469
2470
    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
2471

2472
    if (aisdir != 0 && bisdir == 0)
2473
	return -1;
2474
    if (aisdir == 0 && bisdir != 0)
2475
	return 1;
2476

2477
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2478
2479
}

2480
/* Free our malloc()ed memory */
Chris Allegretta's avatar
Chris Allegretta committed
2481
2482
void free_charptrarray(char **array, int len)
{
2483
2484
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2485
2486
2487
    free(array);
}

2488
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2489
2490
void striponedir(char *foo)
{
2491
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2492

2493
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2494
    /* Don't strip the root dir */
2495
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2496
2497
	return;

2498
2499
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2500
    if (*tmp == '/')
2501
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2502
2503
2504
2505
2506

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

    if (tmp != foo)
2507
2508
2509
2510
2511
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2512
    }
Chris Allegretta's avatar
Chris Allegretta committed
2513
2514
}

2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
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;
}

2525
/* Initialize the browser code, including the list of files in *path */
2526
char **browser_init(const char *path, int *longest, int *numents)
2527
2528
2529
{
    DIR *dir;
    struct dirent *next;
2530
    char **filelist;
2531
    int i = 0;
2532
    size_t path_len;
2533
2534

    dir = opendir(path);
2535
    if (dir == NULL)
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
	return NULL;

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

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

2551
2552
2553
2554
    if (!strcmp(path, "/"))
	path = "";
    path_len = strlen(path);

2555
2556
2557
2558
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;

2559
2560
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2561
2562
	i++;
    }
2563
    closedir(dir);
2564
2565
2566
2567
2568
2569
2570

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2571
/* Our browser function.  inpath is the path to start browsing from */
2572
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2573
2574
2575
2576
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2577
2578
2579
    int numents = 0, i = 0, j = 0, kbinput = -1, meta, longest = 0;
    int abort = 0, col = 0, selected = 0, editline = 0, width = 0;
    int filecols = 0, lineno = 0;
2580
    char **filelist = (char **)NULL;
2581
#ifndef DISABLE_MOUSE
2582
2583
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2584

2585
2586
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2587
2588
2589
    /* If path isn't the same as inpath, we are being passed a new
	dir as an arg.  We free it here so it will be copied from 
	inpath below */
Chris Allegretta's avatar
Chris Allegretta committed
2590
2591
2592
2593
2594
    if (path != NULL && strcmp(path, inpath)) {
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2595
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2596
    if (path == NULL)
2597
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2598
2599

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

2602
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2603
2604
2605
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2606
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2607
2608
2609
2610
2611
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2612
2613

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2614
    do {
2615
2616
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2617
2618
2619

	blank_statusbar_refresh();

2620
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2621
	currshortcut = browser_list;
2622
2623
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2624
2625
 	editline = 0;
	col = 0;
2626
	    
2627
	/* Compute line number we're on now, so we don't divide by zero later */
2628
2629
2630
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2631
2632

	switch (kbinput) {
2633

2634
#ifndef DISABLE_MOUSE
2635
	case KEY_MOUSE:
2636
	    if (getmouse(&mevent) == ERR)
2637
		return retval;
2638
2639
2640
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2641
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2642
2643
2644
2645
		int selectedbackup = selected;

		mevent.y -= 2;

2646
2647
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2648
		selected = (lineno / editwinrows) * editwinrows * width
2649
2650
2651
2652
2653
2654
			+ 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--;
2655
2656
2657

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2658
		if (selected > numents - 1)
2659
		    selected = numents - 1;
2660
		else if (selectedbackup == selected)
2661
2662
2663
2664
		    ungetch('s');	/* Unget the 'select' key */
	    } else	/* Must be clicking a shortcut */
		do_mouse();

2665
2666
            break;
#endif
2667
	case NANO_PREVLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2668
2669
2670
	    if (selected - width >= 0)
		selected -= width;
	    break;
2671
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2672
2673
2674
	    if (selected > 0)
		selected--;
	    break;
2675
	case NANO_NEXTLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2676
2677
2678
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
2679
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2680
2681
2682
2683
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2684
	case NANO_PREVPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2685
	case '-': /* Pico compatibility */
2686
	    if (selected >= (editwinrows + lineno % editwinrows) * width)
2687
		selected -= (editwinrows + lineno % editwinrows) * width; 
Chris Allegretta's avatar
Chris Allegretta committed
2688
2689
2690
2691
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2692
	case NANO_NEXTPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2693
	case ' ': /* Pico compatibility */
2694
2695
	    selected += (editwinrows - lineno % editwinrows) * width;
	    if (selected >= numents)
Chris Allegretta's avatar
Chris Allegretta committed
2696
2697
		selected = numents - 1;
	    break;
2698
2699
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2700
	case '?': /* Pico compatibility */
2701
2702
	     do_help();
	     break;
2703
	case NANO_ENTER_KEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2704
2705
	case 'S': /* Pico compatibility */
	case 's':
Chris Allegretta's avatar
Chris Allegretta committed
2706
	    /* You can't cd up from / */
Rocco Corsi's avatar
   
Rocco Corsi committed
2707
	    if (!strcmp(filelist[selected], "/..") && !strcmp(path, "/")) {
Chris Allegretta's avatar
Chris Allegretta committed
2708
		statusbar(_("Can't move up a directory"));
2709
		beep();
Rocco Corsi's avatar
   
Rocco Corsi committed
2710
2711
2712
		break;
	    }

2713
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2714
2715
2716
	    /* 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. */
2717
	    if (check_operating_dir(filelist[selected], 0) != 0) {
2718
2719
2720
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		beep();
		break;
2721
2722
2723
	    }
#endif

2724
2725
2726
2727
2728
	    if (stat(filelist[selected], &st) == -1) {
		statusbar(_("Can't open \"%s\": %s"), filelist[selected], strerror(errno));
		beep();
		break;
	    }
2729

2730
2731
2732
2733
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2734
2735
	    }

2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
	    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
2746
		}
2747
2748
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2749

2750
2751
2752
2753
2754
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), new_path, strerror(errno));
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2755
	    }
2756
2757
2758
2759
2760
2761
2762

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

Rocco Corsi's avatar
   
Rocco Corsi committed
2763
2764
	/* Goto a specific directory */
	case NANO_GOTO_KEY:
2765
	case NANO_GOTO_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2766
2767
	case 'G': /* Pico compatibility */
	case 'g':
Rocco Corsi's avatar
   
Rocco Corsi committed
2768
	    curs_set(1);
2769
	    j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2770
#ifndef NANO_SMALL
2771
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2772
2773
#endif
		_("Goto Directory"));
2774
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2775
2776
2777
2778
2779
2780
2781
	    curs_set(0);

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

2782
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2783

2784
2785
2786
	    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
2787
2788
	    }

2789
#ifndef DISABLE_OPERATINGDIR
2790
	    if (check_operating_dir(new_path, 0) != 0) {
2791
2792
2793
2794
2795
2796
2797
		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
2798
2799
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2800
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2801
		break;
2802
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2803
2804

	    /* Start over again with the new path value */
2805
2806
	    free_charptrarray(filelist, numents);
	    free(foo);
2807
2808
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2809
2810
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2811
	/* Stuff we want to abort the browser */
2812
	case NANO_CANCEL_KEY:
2813
	case NANO_EXIT_KEY:
2814
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2815
2816
	case 'E': /* Pico compatibility */
	case 'e':
2817
2818
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2819
2820
2821
2822
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2823
2824
	blank_edit();

2825
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
	    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 */
2840
2841
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2842
2843
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2844
2845
2846
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2847
2848
2849
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
2850
		    stat(filelist[j], &st);
2851
2852
2853
2854
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2855
2856
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2857
			(int) st.st_size);
2858
2859
2860
2861
2862
2863
		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);
2864
		else /* It's more than 1 k and less than a meg */
2865
2866
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2867
2868
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2869
	    /* Highlight the currently selected file/dir */
2870
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2871
		wattron(edit, A_REVERSE);
2872
2873
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2874
2875
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2876
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2877
2878
2879
2880
2881
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2882
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2883
2884
2885
2886
2887
2888
2889
2890
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
 	wrefresh(edit);
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2891
    } while ((kbinput = get_kbinput(edit, &meta)) != NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
Chris Allegretta's avatar
Chris Allegretta committed
2892
2893
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2894
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2895
2896
    edit_refresh();

Chris Allegretta's avatar
Chris Allegretta committed
2897
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2898
2899
2900
2901
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2902

2903
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2904
 starts do_browser from there, else from the current dir */
2905
char *do_browse_from(const char *inpath)
2906
2907
{
    struct stat st;
2908
    char *bob;
2909
2910
2911
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2912

2913
2914
2915
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2916

2917
2918
2919
2920
2921
2922
2923
2924
    /*
     * 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);
2925
2926
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2927
	    path = getcwd(NULL, PATH_MAX + 1);
2928
	}
2929
    }
2930

2931
2932
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2933
    if (check_operating_dir(path, 0) != 0)
2934
2935
2936
2937
2938
2939
2940
2941
2942
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2943
    return bob;
2944
}
Chris Allegretta's avatar
Chris Allegretta committed
2945
#endif /* !DISABLE_BROWSER */
2946
2947
2948
2949
2950
2951

#ifndef NANO_SMALL
#ifdef ENABLE_NANORC
void load_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2952
    const struct passwd *userage = NULL;
2953
2954
    static char *nanohist;
    char *buf, *ptr;
Chris Allegretta's avatar
Chris Allegretta committed
2955
    char *homenv = getenv("HOME");
2956
2957
2958
    historyheadtype *history = &search_history;


Chris Allegretta's avatar
Chris Allegretta committed
2959
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2960
        nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
2961
2962
2963
2964
        sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2965
        nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
2966
        sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2967
2968
2969
2970
2971
    }

    /* assume do_rcfile has reported missing home dir */

    if (homenv != NULL || userage != NULL) {
2972
	hist = fopen(nanohist, "r");
2973
	if (hist == NULL) {
2974
2975
2976
            if (errno != ENOENT) {
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2977
		rcfile_error(_("Unable to open ~/.nano_history file, %s"), strerror(errno));
2978
	    }
2979
2980
2981
2982
2983
	    free(nanohist);
	} else {
	    buf = charalloc(1024);
	    while (fgets(buf, 1023, hist) != 0) {
		ptr = buf;
2984
		while (*ptr != '\n' && *ptr != '\0' && ptr < buf + 1023)
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
		    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
3004
    const struct passwd *userage = NULL;
3005
    char *nanohist = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
3006
    char *homenv = getenv("HOME");
3007
3008
3009
3010
3011
3012
3013
    historytype *h;

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

Chris Allegretta's avatar
Chris Allegretta committed
3014
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3015
	nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
3016
3017
3018
3019
	sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3020
	nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
3021
	sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
3022
3023
3024
    }

    if (homenv != NULL || userage != NULL) {
3025
	hist = fopen(nanohist, "wb");
3026
	if (hist == NULL) {
3027
3028
3029
3030
3031
3032
	    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
	} else {
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
	    /* write oldest first */
	    for (h = search_history.tail ; h->prev ; h = h->prev) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3033
		h->data = charealloc(h->data, strlen(h->data) + 2);
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
		    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
		    goto come_from;
		}
	    }
	    if (fputs("\n", hist) == EOF) {
		    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
		    goto come_from;
	    }
	    for (h = replace_history.tail ; h->prev ; h = h->prev) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3045
		h->data = charealloc(h->data, strlen(h->data) + 2);
3046
3047
3048
3049
3050
3051
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
		    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
		    goto come_from;
		}
	    }
3052
  come_from:
3053
3054
3055
3056
3057
3058
	    fclose(hist);
	}
	free(nanohist);
    }
}
#endif /* ENABLE_NANORC */
Chris Allegretta's avatar
Chris Allegretta committed
3059
#endif /* !NANO_SMALL */