files.c 76.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-2003 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

Chris Allegretta's avatar
Chris Allegretta committed
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();
Chris Allegretta's avatar
Chris Allegretta committed
65
    edit_refresh();
66
#endif
Chris Allegretta's avatar
Chris Allegretta committed
67
68
69
70
71
}

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

194
	if (input == '\n') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
195
196
197
198
199
200
201

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

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

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

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

214
215
216
217
	    /* 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
218

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

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

246
247
248
249
250
    /* 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
251
252

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

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

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

284
285
286
287
288
289
290
291
292
    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

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

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

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

327
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
328
329
330
331

    return 1;
}

Chris Allegretta's avatar
Chris Allegretta committed
332
/* Open the file (and decide if it exists). */
Chris Allegretta's avatar
Chris Allegretta committed
333
int open_file(const char *filename, int insert, int quiet)
Chris Allegretta's avatar
Chris Allegretta committed
334
335
{
    int fd;
336
    FILE *f;
Chris Allegretta's avatar
Chris Allegretta committed
337
338
    struct stat fileinfo;

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

    return 1;
}

386
/* This function will return the name of the first available extension
Chris Allegretta's avatar
Chris Allegretta committed
387
388
389
 * 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
390
char *get_next_filename(const char *name)
391
392
393
394
395
396
397
398
{
    int i = 0;
    char *buf = NULL;
    struct stat fs;

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

Chris Allegretta's avatar
Chris Allegretta committed
399
    while (1) {
400
401

	if (stat(buf, &fs) == -1)
Chris Allegretta's avatar
Chris Allegretta committed
402
	    return buf;
403
404
405
406
407
408
409
410
	if (i == INT_MAX)
	    break;

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

Chris Allegretta's avatar
Chris Allegretta committed
411
412
    /* We get here only if there is no possible save file. */
    buf[0] = '\0';
413
414
415
416

    return buf;
}

417
int do_insertfile(int loading_file)
Chris Allegretta's avatar
Chris Allegretta committed
418
{
Chris Allegretta's avatar
Chris Allegretta committed
419
    int i, old_current_x = current_x;
420
    char *realname = NULL;
421
422
    static char *inspath = NULL;

423
424
425
426
    if (inspath == NULL) {
	inspath = charalloc(1);
	inspath[0] = '\0';
    }
Chris Allegretta's avatar
Chris Allegretta committed
427
428

    wrap_reset();
429

430
431
432
433
#if !defined(DISABLE_BROWSER) || !defined(NANO_SMALL) && defined(ENABLE_MULTIBUFFER)
  start_again:	/* Goto here when the user cancels the file browser. */
#endif

434
#if !defined(DISABLE_BROWSER) || (!defined(DISABLE_MOUSE) && defined(NCURSES_MOUSE_VERSION))
435
    currshortcut = insertfile_list;
436
437
#endif

438
#ifndef DISABLE_OPERATINGDIR
439
    if (operating_dir != NULL && strcmp(operating_dir, ".") != 0)
440
441
#ifdef ENABLE_MULTIBUFFER 
	if (ISSET(MULTIBUFFER))
Chris Allegretta's avatar
Chris Allegretta committed
442
443
444
445
446
	    i = statusq(1, insertfile_list, inspath,
#ifndef NANO_SMALL
		0,
#endif
		_("File to insert into new buffer [from %s] "),
447
448
449
		operating_dir);
	else
#endif
Chris Allegretta's avatar
Chris Allegretta committed
450
451
452
453
454
	    i = statusq(1, insertfile_list, inspath,
#ifndef NANO_SMALL
		0,
#endif
		_("File to insert [from %s] "),
455
		operating_dir);
456

457
    else
458
#endif
459
460
#ifdef ENABLE_MULTIBUFFER 
	if (ISSET(MULTIBUFFER))
Chris Allegretta's avatar
Chris Allegretta committed
461
462
463
464
465
	    i = statusq(1, insertfile_list, inspath,
#ifndef NANO_SMALL
		0,
#endif
		_("File to insert into new buffer [from ./] "));
466
	else
Chris Allegretta's avatar
Chris Allegretta committed
467
468
469
470
#endif /* ENABLE_MULTIBUFFER */
	    i = statusq(1, insertfile_list, inspath,
#ifndef NANO_SMALL
		0,
471
#endif
Chris Allegretta's avatar
Chris Allegretta committed
472
		_("File to insert [from ./] "));
473

Chris Allegretta's avatar
Chris Allegretta committed
474
    if (i != -1) {
475
	inspath = mallocstrcpy(inspath, answer);
Chris Allegretta's avatar
Chris Allegretta committed
476
#ifdef DEBUG
477
	fprintf(stderr, _("filename is %s\n"), answer);
Chris Allegretta's avatar
Chris Allegretta committed
478
479
#endif

480
#ifndef NANO_SMALL
481
#ifdef ENABLE_MULTIBUFFER
482
	if (i == TOGGLE_LOAD_KEY) {
483
484
	    /* Don't allow toggling if we're in view mode. */
	    if (!ISSET(VIEW_MODE))
485
		TOGGLE(MULTIBUFFER);
486
487
	    loading_file = ISSET(MULTIBUFFER);
	    goto start_again;
488
	}
489
#endif /* ENABLE_MULTIBUFFER */
490

491
	if (i == NANO_EXTCMD_KEY) {
492
493
494
	    int ts = statusq(TRUE, extcmd_list, "", NULL, 
		_("Command to execute"));
	    if (ts  == -1 || answer == NULL || answer[0] == '\0') {
495
		statusbar(_("Cancelled"));
496
497
498
499
500
		UNSET(KEEP_CUTBUFFER);
		display_main_list();
		return 0;
	    }
	}
501
#endif /* !NANO_SMALL */
502
503
504
505
#ifndef DISABLE_BROWSER
	if (i == NANO_TOFILES_KEY) {
	    char *tmp = do_browse_from(answer);

506
507
	    if (tmp != NULL) {
		free(answer);
508
		answer = tmp;
509
	    } else
510
511
512
513
514
515
516
517
518
519
520
		goto start_again;
	}
#endif

#ifndef DISABLE_OPERATINGDIR
	if (i != NANO_EXTCMD_KEY && check_operating_dir(answer, FALSE)) {
	    statusbar(_("Can't insert file from outside of %s"),
			operating_dir);
	    return 0;
	}
#endif
521

522
#ifdef ENABLE_MULTIBUFFER
523
	if (loading_file) {
524
525
	    /* update the current entry in the open_files structure */
	    add_open_file(1);
526
527
	    new_file();
	    UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
528
529
530
#ifndef NANO_SMALL
	    UNSET(MARK_ISSET);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
531
532
533
	}
#endif

534
#ifndef NANO_SMALL
535
536
	if (i == NANO_EXTCMD_KEY) {
	    realname = mallocstrcpy(realname, "");
537
	    i = open_pipe(answer);
538
	} else
539
#endif /* NANO_SMALL */
540
541
	{
	    realname = real_dir_from_tilde(answer);
542
	    i = open_file(realname, 1, loading_file);
543
	}
544

545
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
546
547
548
549
550
551
552
553
554
555
556
557
558
559
	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);
	}
560
561
#endif

562
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
563

Chris Allegretta's avatar
Chris Allegretta committed
564
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
565
	dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
566
#endif
567

568
#ifdef ENABLE_MULTIBUFFER
569
	if (loading_file)
570
	    load_file(0);
571
572
573
	else
#endif
	    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
574
575

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

578
#ifdef ENABLE_MULTIBUFFER
579
580
581
582
583
584
585
586
587
588
	/* 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
589
590
591
592
593
594
595
596
597
598
599
#ifdef ENABLE_MULTIBUFFER
	if (!loading_file) {
#endif

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

#ifdef ENABLE_MULTIBUFFER
	}
#endif

600
	/* If we've gone off the bottom, recenter; otherwise, just redraw */
Chris Allegretta's avatar
Chris Allegretta committed
601
	if (current->lineno > editbot->lineno)
602
	    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
603
604
605
606
607
	else
	    edit_refresh();

    } else {
	statusbar(_("Cancelled"));
608
	i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
609
    }
610

Chris Allegretta's avatar
Chris Allegretta committed
611
612
613
614
#ifdef ENABLE_MULTIBUFFER
  skip_insert:
#endif

615
616
617
618
619
620
    free(inspath);
    inspath = NULL;

    UNSET(KEEP_CUTBUFFER);
    display_main_list();
    return i;
Chris Allegretta's avatar
Chris Allegretta committed
621
622
}

623
624
625
int do_insertfile_void(void)
{
    int result = 0;
626
#ifdef ENABLE_MULTIBUFFER
627
628
629
630
631
632
633
634
    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));
635
636
637
638
639
640
641
642
#else
    result = do_insertfile(0);
#endif

    display_main_list();
    return result;
}

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

    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
703
	    fprintf(stderr, _("%s: free'd a node, YAY!\n"), "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
704
705
706
707
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
708
	fprintf(stderr, _("%s: free'd last node.\n"), "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
709
710
711
712
#endif
    }
}

713
/*
714
 * Add/update an entry to the open_files openfilestruct.  If update is
715
 * zero, a new entry is created; otherwise, the current entry is updated.
716
 * Return 0 on success or 1 on error.
717
 */
718
int add_open_file(int update)
719
{
720
    openfilestruct *tmp;
721

722
    if (fileage == NULL || current == NULL || filename == NULL)
723
724
725
	return 1;

    /* if no entries, make the first one */
726
    if (open_files == NULL)
727
	open_files = make_new_opennode(NULL);
728
729
730
731
732
733
734

    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
735
	fprintf(stderr, _("filename is %s\n"), open_files->filename);
736
737
#endif

738
739
	tmp = make_new_opennode(NULL);
	splice_opennode(open_files, tmp, open_files->next);
740
741
742
743
	open_files = open_files->next;
    }

    /* save current filename */
744
    open_files->filename = mallocstrcpy(open_files->filename, filename);
745

746
747
748
749
750
#ifndef NANO_SMALL
    /* save the file's stat */
    open_files->originalfilestat = originalfilestat;
#endif

751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
    /* 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 */
767
    open_files->file_lineno = current->lineno;
768

Chris Allegretta's avatar
Chris Allegretta committed
769
770
771
772
773
774
775
776
777
778
779
780
781
782
    /* if we're updating, save current modification status (and marking
       status, if available) */
    if (update) {
#ifndef NANO_SMALL
	open_files->file_flags = (MODIFIED & ISSET(MODIFIED)) | (MARK_ISSET & ISSET(MARK_ISSET));
	if (ISSET(MARK_ISSET)) {
	    open_files->file_mark_beginbuf = mark_beginbuf;
	    open_files->file_mark_beginx = mark_beginx;
	}
#else
	open_files->file_flags = (MODIFIED & ISSET(MODIFIED));
#endif
    }

783
784
785
    /* 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
786
787
788
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
789
    }
790
791

#ifdef DEBUG
792
    fprintf(stderr, _("filename is %s\n"), open_files->filename);
793
794
795
796
797
798
799
800
801
802
803
804
#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)
{
805
    if (open_files == NULL)
806
807
808
809
	return 1;

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

Chris Allegretta's avatar
Chris Allegretta committed
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
    /* 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
835

Chris Allegretta's avatar
Chris Allegretta committed
836
837
838
839
#ifdef ENABLE_COLOR
    update_color();
#endif

840
841
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
842
    do_gotopos(open_files->file_lineno, open_files->file_current_x, open_files->file_current_y, open_files->file_placewewant);
843

Chris Allegretta's avatar
Chris Allegretta committed
844
    /* update the titlebar */
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
    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)
{
860
    if (open_files == NULL)
861
862
863
	return 1;

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

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

	/* only one file open */
	if (!closing_file)
	    statusbar(_("No more open files"));
	return 1;
    }

876
    if (open_files->prev != NULL) {
877
878
879
	open_files = open_files->prev;

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

    }

885
    else if (open_files->next != NULL) {
886
887

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

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

    }

    load_open_file();

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

902
903
904
905
906
907
908
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

909
910
911
/* This function is used by the shortcut list. */
int open_prevfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
912
    return open_prevfile(0);
913
914
}

915
916
917
918
919
920
921
922
/*
 * 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)
{
923
    if (open_files == NULL)
924
925
926
	return 1;

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

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

	/* only one file open */
	if (!closing_file)
	    statusbar(_("No more open files"));
	return 1;
    }

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

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

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

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

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

	}
    }

    load_open_file();

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

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

    return 0;
}

972
973
974
/* This function is used by the shortcut list. */
int open_nextfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
975
    return open_nextfile(0);
976
977
}

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

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

Chris Allegretta's avatar
Chris Allegretta committed
990
991
992
    /* make sure open_files->fileage and fileage, and open_files->filebot
       and filebot, are in sync; they might not be if lines have been cut
       from the top or bottom of the file */
993
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
994
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
995

996
    tmp = open_files;
997
998
    if (open_nextfile(1)) {
	if (open_prevfile(1))
999
1000
1001
	    return 1;
    }

1002
1003
    unlink_opennode(tmp);
    delete_opennode(tmp);
1004
1005
1006
1007
1008

    shortcut_init(0);
    display_main_list();
    return 0;
}
1009
#endif /* MULTIBUFFER */
1010

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

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

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

1036
    if (d_here != NULL) {
1037
1038

	align(&d_here);
1039
1040
1041
1042
	if (strcmp(d_here, "/")) {
	    d_here = nrealloc(d_here, strlen(d_here) + 2);
	    strcat(d_here, "/");
	}
1043
1044
1045
1046
1047

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

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

1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
	/* 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 != '/') {
		d_there = nrealloc(d_there, strlen(d_there) + 2);
		strcat(d_there, "/");
		d_there_file = nrealloc(d_there_file, strlen(d_there_file) + 2);
		strcat(d_there_file, "/");
	    }
	}

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

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

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

		free(d_there);

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

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

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

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

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

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

    return newpath;
}
1148
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1149
1150
1151

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

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

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

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

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

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

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

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

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

1225
    full_tempdir = nrealloc(full_tempdir, strlen(full_tempdir) + 12);
1226
1227

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

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

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

1252
    if (operating_dir == NULL)
1253
1254
1255
1256
1257
	return;
    full_operating_dir = get_full_path(operating_dir);

    /* If get_full_path() failed or the operating directory is
       inaccessible, unset operating_dir. */
1258
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1259
1260
1261
1262
1263
1264
1265
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1266
1267
1268
1269
1270
1271
/*
 * Check to see if we're inside the operating directory.  Return 0 if we
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
 * completion will work.
 */
1272
int check_operating_dir(const char *currpath, int allow_tabcomp)
1273
{
1274
1275
1276
    /* The char *full_operating_dir is global for mem cleanup, and
       therefore we only need to get it the first time this function
       is called; also, a relative operating directory path will
1277
1278
       only be handled properly if this is done */

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

    /* if no operating directory is set, don't bother doing anything */
1284
    if (operating_dir == NULL)
1285
1286
1287
	return 0;

    fullpath = get_full_path(currpath);
1288
    if (fullpath == NULL)
1289
1290
1291
1292
1293
1294
1295
	return 1;

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

    /* if both searches failed, we're outside the operating directory */
1296
    /* otherwise */
1297
1298
1299
1300
1301
    /* 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 */
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1302
1303
	retval = 1;
    free(fullpath);	
1304
    /* otherwise, we're still inside it */
1305
    return retval;
1306
}
1307
1308
#endif

Chris Allegretta's avatar
Chris Allegretta committed
1309
1310
/*
 * Write a file out.  If tmp is nonzero, we set the umask to 0600,
1311
 * we don't set the global variable filename to its name, and don't
Chris Allegretta's avatar
Chris Allegretta committed
1312
 * print out how many lines we wrote on the statusbar.
Chris Allegretta's avatar
Chris Allegretta committed
1313
 *
1314
 * tmp means we are writing a tmp file in a secure fashion.  We use
1315
 * it when spell checking or dumping the file on an error.
1316
 *
1317
1318
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1319
1320
 *
 * nonamechange means don't change the current filename, it is ignored
1321
 * if tmp is nonzero or if we're appending/prepending.
Chris Allegretta's avatar
Chris Allegretta committed
1322
 */
1323
int write_file(const char *name, int tmp, int append, int nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1324
{
1325
1326
1327
    int retval = -1;
	/* Instead of returning in this function, you should always
	 * merely set retval then goto cleanup_and_exit. */
1328
1329
    long size;
    int lineswritten = 0;
1330
    char *buf = NULL;
1331
    const filestruct *fileptr;
1332
1333
1334
    FILE *f;
    int fd;
    int mask = 0, realexists, anyexists;
1335
    struct stat st, lst;
1336
    char *realname = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
1337

Chris Allegretta's avatar
Chris Allegretta committed
1338
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1339
1340
1341
	statusbar(_("Cancelled"));
	return -1;
    }
1342
1343
    if (!tmp)
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1344
    fileptr = fileage;
1345

1346
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1347

1348
#ifndef DISABLE_OPERATINGDIR
1349
1350
    /* If we're writing a temporary file, we're probably going outside
       the operating directory, so skip the operating directory test. */
1351
    if (!tmp && operating_dir != NULL && check_operating_dir(realname, 0)) {
1352
1353
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1354
1355
1356
    }
#endif

1357
1358
    /* Save the state of file at the end of the symlink (if there is
       one). */
1359
1360
    realexists = stat(realname, &st);

1361
1362
1363
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
       temporary, and the file already exists.  Furthermore, if we aren't
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1364
       appending, prepending, or writing a selection, we backup only if
1365
1366
1367
       the file has not been modified by someone else since nano opened
       it. */
    if (ISSET(BACKUP_FILE) && !tmp && realexists == 0 &&
1368
	    (append != 0 || ISSET(MARK_ISSET) ||
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
		originalfilestat.st_mtime == st.st_mtime)) {
	FILE *backup_file;
	char *backupname = NULL;
	char backupbuf[COPYFILEBLOCKSIZE];
	size_t bytesread;
	struct utimbuf filetime;

	/* save the original file's access and modification times */
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

	/* open the original file to copy to the backup */
	f = fopen(realname, "rb");
1382
	if (f == NULL) {
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
	    statusbar(_("Could not read %s for backup: %s"), realname,
		strerror(errno));
	    return -1;
	}

	backupname = charalloc(strlen(realname) + 2);
	sprintf(backupname, "%s~", realname);

	/* get a file descriptor for the destination backup file */
	backup_file = fopen(backupname, "wb");
1393
	if (backup_file == NULL) {
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
	    statusbar(_("Couldn't write backup: %s"), strerror(errno));
	    free(backupname);
	    return -1;
	}

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

	/* copy the file */
	while ((bytesread = fread(backupbuf, sizeof(char),
		COPYFILEBLOCKSIZE, f)) > 0)
	    if (fwrite(backupbuf, sizeof(char), bytesread, backup_file) <= 0)
		break;
	fclose(backup_file);
	fclose(f);

	if (chmod(backupname, originalfilestat.st_mode) == -1)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1412
	    statusbar(_("Could not set permissions %o on backup %s: %s"),
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
			originalfilestat.st_mode, backupname,
			strerror(errno));

	if (chown(backupname, originalfilestat.st_uid,
		originalfilestat.st_gid) == -1)
	    statusbar(_("Could not set owner %d/group %d on backup %s: %s"),
			originalfilestat.st_uid, originalfilestat.st_gid,
			backupname, strerror(errno));

	if (utime(backupname, &filetime) == -1)
	    statusbar(_("Could not set access/modification time on backup %s: %s"),
			backupname, strerror(errno));

	free(backupname);
    }
#endif

1430
1431
    /* Stat the link itself for the check... */
    anyexists = lstat(realname, &lst);
1432

1433
1434
    /* New case: if the file exists, just give up */
    if (tmp && anyexists != -1)
1435
	goto cleanup_and_exit;
1436
    /* NOTE: If you change this statement, you MUST CHANGE the if 
1437
       statement below (that says:
1438
		if (realexists == -1 || tmp || (ISSET(NOFOLLOW_SYMLINKS) &&
1439
		S_ISLNK(lst.st_mode))) {
1440
       to reflect whether or not to link/unlink/rename the file */
1441
    else if (append != 2 && (!ISSET(NOFOLLOW_SYMLINKS) || !S_ISLNK(lst.st_mode) 
1442
		|| tmp)) {
1443
1444
1445
	/* Use O_EXCL if tmp is nonzero.  This is now copied from joe,
	   because wiggy says so *shrug*. */
	if (append != 0)
Chris Allegretta's avatar
Chris Allegretta committed
1446
	    fd = open(realname, O_WRONLY | O_CREAT | O_APPEND, (S_IRUSR | S_IWUSR));
1447
	else if (tmp)
Chris Allegretta's avatar
Chris Allegretta committed
1448
	    fd = open(realname, O_WRONLY | O_CREAT | O_EXCL, (S_IRUSR | S_IWUSR));
1449
	else
Chris Allegretta's avatar
Chris Allegretta committed
1450
	    fd = open(realname, O_WRONLY | O_CREAT | O_TRUNC, (S_IRUSR | S_IWUSR));
1451
1452

	/* First, just give up if we couldn't even open the file */
1453
	if (fd == -1) {
1454
	    if (!tmp && ISSET(TEMP_OPT)) {
Chris Allegretta's avatar
Chris Allegretta committed
1455
		UNSET(TEMP_OPT);
1456
1457
1458
1459
1460
		retval = do_writeout(filename, 1, 0);
	    } else
		statusbar(_("Could not open file for writing: %s"),
			strerror(errno));
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1461
	}
1462

Chris Allegretta's avatar
Chris Allegretta committed
1463
1464
1465
    }
    /* Don't follow symlink.  Create new file. */
    else {
1466
	buf = charalloc(strlen(realname) + 8);
1467
	strcpy(buf, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1468
1469
	strcat(buf, ".XXXXXX");
	if ((fd = mkstemp(buf)) == -1) {
Chris Allegretta's avatar
Chris Allegretta committed
1470
1471
	    if (ISSET(TEMP_OPT)) {
		UNSET(TEMP_OPT);
1472
1473
1474
1475
1476
		retval = do_writeout(filename, 1, 0);
	    } else
		statusbar(_("Could not open file for writing: %s"),
			strerror(errno));
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1477
1478
1479
	}
    }

Chris Allegretta's avatar
Chris Allegretta committed
1480
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
1481
    dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
1482
#endif
1483

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1484
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1485
    if (f == NULL) {
1486
1487
	statusbar(_("Could not open file for writing: %s"), strerror(errno));
	goto cleanup_and_exit;
1488
1489
    }

Chris Allegretta's avatar
Chris Allegretta committed
1490
    while (fileptr != NULL && fileptr->next != NULL) {
1491
	int data_len;
1492

Robert Siemborski's avatar
Robert Siemborski committed
1493
	/* Next line is so we discount the "magic line" */
1494
1495
	if (filebot == fileptr && fileptr->data[0] == '\0')
	    break;
Robert Siemborski's avatar
Robert Siemborski committed
1496

1497
	data_len = strlen(fileptr->data);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1498
1499
1500
1501

	/* newlines to nulls, just before we write to disk */
	sunder(fileptr->data);

1502
	size = fwrite(fileptr->data, 1, data_len, f);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1503
1504
1505
1506

	/* nulls to newlines; data_len is the string's real length here */
	unsunder(fileptr->data, data_len);

1507
	if (size < data_len) {
Chris Allegretta's avatar
Chris Allegretta committed
1508
1509
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
1510
	    fclose(f);
1511
	    goto cleanup_and_exit;
1512
	}
Chris Allegretta's avatar
Chris Allegretta committed
1513
#ifdef DEBUG
1514
	else
Chris Allegretta's avatar
Chris Allegretta committed
1515
1516
	    fprintf(stderr, _("Wrote >%s\n"), fileptr->data);
#endif
1517
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1518
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1519
	    putc('\r', f);
Chris Allegretta's avatar
Chris Allegretta committed
1520
1521

	if (!ISSET(MAC_FILE))
1522
#endif
1523
	    putc('\n', f);
Chris Allegretta's avatar
Chris Allegretta committed
1524
1525
1526
1527
1528
1529

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

    if (fileptr != NULL) {
1530
	int data_len = strlen(fileptr->data);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1531
1532
1533
1534

	/* newlines to nulls, just before we write to disk */
	sunder(fileptr->data);

1535
	size = fwrite(fileptr->data, 1, data_len, f);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1536
1537
1538
1539

	/* nulls to newlines; data_len is the string's real length here */
	unsunder(fileptr->data, data_len);

1540
	if (size < data_len) {
Chris Allegretta's avatar
Chris Allegretta committed
1541
1542
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
1543
	    goto cleanup_and_exit;
1544
	} else if (data_len > 0) {
1545
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1546
	    if (ISSET(DOS_FILE) || ISSET(MAC_FILE)) {
1547
		if (putc('\r', f) == EOF) {
1548
1549
		    statusbar(_("Could not open file for writing: %s"),
			  strerror(errno));
1550
		    fclose(f);
1551
		    goto cleanup_and_exit;
1552
		}
1553
		lineswritten++;
1554
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1555
1556

	    if (!ISSET(MAC_FILE))
1557
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1558
	    {
1559
		if (putc('\n', f) == EOF) {
Chris Allegretta's avatar
Chris Allegretta committed
1560
		    statusbar(_("Could not open file for writing: %s"),
Chris Allegretta's avatar
Chris Allegretta committed
1561
			  strerror(errno));
1562
		    fclose(f);
1563
		    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1564
		}
1565
		lineswritten++;
Chris Allegretta's avatar
Chris Allegretta committed
1566
1567
1568
1569
	    }
	}
    }

1570
    if (fclose(f) != 0) {
1571
	statusbar(_("Could not close %s: %s"), realname, strerror(errno));
Chris Allegretta's avatar
Chris Allegretta committed
1572
	unlink(buf);
1573
	goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1574
1575
    }

1576
1577
    /* if we're prepending, open the real file, and append it here */
    if (append == 2) {
1578
1579
1580
	int fd_source, fd_dest;
	FILE *f_source, *f_dest;
	int prechar;
1581

1582
	if ((fd_dest = open(buf, O_WRONLY | O_APPEND, (S_IRUSR | S_IWUSR))) == -1) {
1583
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
1584
	    goto cleanup_and_exit;
1585
	}
1586
	f_dest = fdopen(fd_dest, "wb");
1587
	if (f_dest == NULL) {
1588
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
1589
	    close(fd_dest);
1590
	    goto cleanup_and_exit;
1591
	}
1592
	if ((fd_source = open(realname, O_RDONLY | O_CREAT)) == -1) {
1593
	    statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
1594
	    fclose(f_dest);
1595
	    goto cleanup_and_exit;
1596
1597
	}
	f_source = fdopen(fd_source, "rb");
1598
	if (f_source == NULL) {
1599
1600
1601
	    statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
	    fclose(f_dest);
	    close(fd_source);
1602
	    goto cleanup_and_exit;
1603
1604
	}

1605
1606
1607
1608
1609
1610
        /* Doing this in blocks is an exercise left to some other reader. */
	while ((prechar = getc(f_source)) != EOF) {
	    if (putc(prechar, f_dest) == EOF) {
		statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
		fclose(f_source);
		fclose(f_dest);
1611
		goto cleanup_and_exit;
1612
1613
	    }
	}
1614

1615
1616
1617
1618
	if (ferror(f_source)) {
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
	    fclose(f_source);
	    fclose(f_dest);
1619
	    goto cleanup_and_exit;
1620
1621
1622
1623
	}
	    
	fclose(f_source);
	fclose(f_dest);
1624
1625
    }

1626
    if (realexists == -1 || tmp ||
1627
	(ISSET(NOFOLLOW_SYMLINKS) && S_ISLNK(lst.st_mode))) {
Chris Allegretta's avatar
Chris Allegretta committed
1628

1629
	/* Use default umask as file permissions if file is a new file. */
1630
1631
	mask = umask(0);
	umask(mask);
Chris Allegretta's avatar
Chris Allegretta committed
1632

1633
	if (tmp)	/* We don't want anyone reading our temporary file! */
Chris Allegretta's avatar
Chris Allegretta committed
1634
	    mask = S_IRUSR | S_IWUSR;
1635
	else
Chris Allegretta's avatar
Chris Allegretta committed
1636
1637
	    mask = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH |
		S_IWOTH) & ~mask;
1638
    } else
Chris Allegretta's avatar
Chris Allegretta committed
1639
	/* Use permissions from file we are overwriting. */
1640
1641
	mask = st.st_mode;

1642
    if (append == 2 || 
1643
		(!tmp && (ISSET(NOFOLLOW_SYMLINKS) && S_ISLNK(lst.st_mode)))) {
1644
1645
	if (unlink(realname) == -1) {
	    if (errno != ENOENT) {
1646
		statusbar(_("Could not open %s for writing: %s"),
1647
			  realname, strerror(errno));
1648
		unlink(buf);
1649
		goto cleanup_and_exit;
1650
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1651
	}
1652
1653
1654
1655
1656
1657
	if (link(buf, realname) != -1)
	    unlink(buf);
	else if (errno != EPERM) {
	    statusbar(_("Could not open %s for writing: %s"),
		      name, strerror(errno));
	    unlink(buf);
1658
	    goto cleanup_and_exit;
1659
1660
1661
1662
	} else if (rename(buf, realname) == -1) {	/* Try a rename?? */
	    statusbar(_("Could not open %s for writing: %s"),
		      realname, strerror(errno));
	    unlink(buf);
1663
	    goto cleanup_and_exit;
1664
	}
Chris Allegretta's avatar
Chris Allegretta committed
1665
    }
1666
1667
1668
1669
    if (chmod(realname, mask) == -1)
	statusbar(_("Could not set permissions %o on %s: %s"),
		  mask, realname, strerror(errno));

1670
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1671
	if (!nonamechange) {
1672
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1673
1674
1675
1676
1677
#ifdef ENABLE_COLOR
	    update_color();
	    edit_refresh();
#endif
	}
1678

1679
1680
1681
1682
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1683
	statusbar(P_("Wrote %d line", "Wrote %d lines", lineswritten),
1684
			lineswritten);
1685
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1686
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1687
    }
1688
1689
1690
1691
1692
1693
1694

    retval = 1;

  cleanup_and_exit:
    free(realname);
    free(buf);
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1695
1696
}

1697
int do_writeout(const char *path, int exiting, int append)
Chris Allegretta's avatar
Chris Allegretta committed
1698
1699
{
    int i = 0;
1700
1701
1702
#ifdef NANO_EXTRA
    static int did_cred = 0;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1703

1704
#if !defined(DISABLE_BROWSER) || (!defined(DISABLE_MOUSE) && defined(NCURSES_MOUSE_VERSION))
1705
    currshortcut = writefile_list;
1706
1707
#endif

1708
    answer = mallocstrcpy(answer, path);
Chris Allegretta's avatar
Chris Allegretta committed
1709

1710
    if (exiting && ISSET(TEMP_OPT)) {
1711
	if (filename[0] != '\0') {
1712
	    i = write_file(answer, 0, 0, 0);
1713
1714
	    display_main_list();
	    return i;
1715
	} else {
Chris Allegretta's avatar
Chris Allegretta committed
1716
1717
1718
1719
1720
	    UNSET(TEMP_OPT);
	    do_exit();

	    /* They cancelled, abort quit */
	    return -1;
1721
	}
Chris Allegretta's avatar
Chris Allegretta committed
1722
1723
1724
    }

    while (1) {
1725
#ifndef NANO_SMALL
1726
1727
	const char *formatstr, *backupstr;

1728
1729
1730
1731
1732
1733
1734
	if (ISSET(MAC_FILE))
	   formatstr = _(" [Mac Format]");
	else if (ISSET(DOS_FILE))
	   formatstr = _(" [DOS Format]");
	else
	   formatstr = "";

1735
1736
1737
1738
1739
	if (ISSET(BACKUP_FILE))
	   backupstr = _(" [Backup]");
	else
	   backupstr = "";

1740
	/* Be nice to the translation folks */
1741
	if (ISSET(MARK_ISSET) && !exiting) {
1742
	    if (append == 2)
1743
		i = statusq(1, writefile_list, "", 0,
1744
		    "%s%s%s", _("Prepend Selection to File"), formatstr, backupstr);
Chris Allegretta's avatar
Chris Allegretta committed
1745
	    else if (append == 1)
1746
		i = statusq(1, writefile_list, "", 0,
1747
		    "%s%s%s", _("Append Selection to File"), formatstr, backupstr);
1748
	    else
1749
		i = statusq(1, writefile_list, "", 0,
1750
1751
		    "%s%s%s", _("Write Selection to File"), formatstr, backupstr);
	} else {
1752
	    if (append == 2)
1753
		i = statusq(1, writefile_list, answer, 0,
1754
		    "%s%s%s", _("File Name to Prepend to"), formatstr, backupstr);
Chris Allegretta's avatar
Chris Allegretta committed
1755
	    else if (append == 1)
1756
		i = statusq(1, writefile_list, answer, 0,
1757
		    "%s%s%s", _("File Name to Append to"), formatstr, backupstr);
1758
	    else
1759
		i = statusq(1, writefile_list, answer, 0,
1760
		    "%s%s%s", _("File Name to Write"), formatstr, backupstr);
1761
	}
1762
1763
#else
	if (append == 2)
Chris Allegretta's avatar
Chris Allegretta committed
1764
	    i = statusq(1, writefile_list, answer,
1765
		"%s", _("File Name to Prepend to"));
Chris Allegretta's avatar
Chris Allegretta committed
1766
1767
	else if (append == 1)
	    i = statusq(1, writefile_list, answer,
1768
1769
		"%s", _("File Name to Append to"));
	else
Chris Allegretta's avatar
Chris Allegretta committed
1770
	    i = statusq(1, writefile_list, answer,
1771
1772
		"%s", _("File Name to Write"));
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1773

1774
1775
1776
1777
1778
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
	    return 0;
	}
Chris Allegretta's avatar
Chris Allegretta committed
1779

1780
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1781
	if (i == NANO_TOFILES_KEY) {
1782
	    char *tmp = do_browse_from(answer);
1783

1784
	    currshortcut = writefile_list;
1785
1786
1787
1788
	    if (tmp == NULL)
		continue;
	    free(answer);
	    answer = tmp;
1789
	} else
1790
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1791
#ifndef NANO_SMALL
1792
1793
1794
	if (i == TOGGLE_DOS_KEY) {
	    UNSET(MAC_FILE);
	    TOGGLE(DOS_FILE);
1795
	    continue;
1796
1797
1798
	} else if (i == TOGGLE_MAC_KEY) {
	    UNSET(DOS_FILE);
	    TOGGLE(MAC_FILE);
1799
	    continue;
1800
1801
	} else if (i == TOGGLE_BACKUP_KEY) {
	    TOGGLE(BACKUP_FILE);
1802
1803
	    continue;
	} else
1804
#endif /* !NANO_SMALL */
1805
1806
1807
1808
1809
1810
1811
	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
1812

Chris Allegretta's avatar
Chris Allegretta committed
1813
#ifdef DEBUG
1814
	fprintf(stderr, _("filename is %s\n"), answer);
Chris Allegretta's avatar
Chris Allegretta committed
1815
#endif
1816
1817

#ifdef NANO_EXTRA
1818
	if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy")
1819
		&& !did_cred) {
1820
1821
1822
1823
	    do_credits();
	    did_cred = 1;
	    return -1;
	}
1824
#endif
1825
	if (append == 0 && strcmp(answer, filename) != 0) {
1826
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1827

1828
1829
1830
1831
1832
	    if (!stat(answer, &st)) {
		i = do_yesno(0, 0, _("File exists, OVERWRITE ?"));

		if (i == 0 || i == -1)
		    continue;
Chris Allegretta's avatar
Chris Allegretta committed
1833
	    }
1834
	}
Chris Allegretta's avatar
Chris Allegretta committed
1835

1836
#ifndef NANO_SMALL
1837
1838
1839
	/* Here's where we allow the selected text to be written to 
	   a separate file. */
	if (ISSET(MARK_ISSET) && !exiting) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1840
	    filestruct *fileagebak = fileage;
1841
1842
	    filestruct *filebotbak = filebot;
	    filestruct *cutback = cutbuffer;
1843
1844
	    int oldmod = ISSET(MODIFIED);
		/* write_file() unsets the MODIFIED flag. */
1845

1846
	    cutbuffer = NULL;
1847

1848
1849
1850
	    /* Put the marked text in the cutbuffer without changing
	       the open file. */
	    cut_marked_segment(current, current_x, mark_beginbuf,
1851
1852
1853
				mark_beginx, 0);

	    fileage = cutbuffer;
1854
	    filebot = get_cutbottom();
1855
	    i = write_file(answer, 0, append, 1);
1856
1857

	    /* Now restore everything */
1858
	    free_filestruct(cutbuffer);
1859
1860
1861
1862
1863
1864
	    fileage = fileagebak;
	    filebot = filebotbak;
	    cutbuffer = cutback;
	    if (oldmod)
		set_modified();
	} else
1865
#endif /* !NANO_SMALL */
1866
	    i = write_file(answer, 0, append, 0);
1867

1868
#ifdef ENABLE_MULTIBUFFER
1869
1870
1871
1872
	/* If we're not about to exit, update the current entry in
	   the open_files structure. */
	if (!exiting)
	    add_open_file(1);
1873
#endif
1874
1875
1876
	display_main_list();
	return i;
    } /* while (1) */
Chris Allegretta's avatar
Chris Allegretta committed
1877
1878
1879
1880
}

int do_writeout_void(void)
{
1881
    return do_writeout(filename, 0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
1882
}
Chris Allegretta's avatar
Chris Allegretta committed
1883

1884
/* Return a malloc()ed string containing the actual directory, used
1885
1886
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1887
{
Chris Allegretta's avatar
Chris Allegretta committed
1888
    char *dirtmp = NULL;
1889

1890
    if (buf[0] == '~') {
1891
1892
1893
1894
1895
1896
1897
1898
	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++)
	    ;

	if (i == 1) {
Chris Allegretta's avatar
Chris Allegretta committed
1899
1900
1901
	    /* Determine home directory using getpwent(), don't rely on
	       $HOME */
	    uid_t euid = geteuid();
1902

Chris Allegretta's avatar
Chris Allegretta committed
1903
1904
1905
	    do {
		userdata = getpwent();
	    } while (userdata != NULL && userdata->pw_uid != euid);
1906
1907
1908
1909
1910
	} else {
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
			strncmp(userdata->pw_name, buf + 1, i - 1));
Chris Allegretta's avatar
Chris Allegretta committed
1911
1912
	}
	endpwent();
1913

1914
	if (userdata != NULL) {	/* User found */
1915
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1916
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1917
	}
1918
    }
1919

1920
1921
1922
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1923
    return dirtmp;
1924
1925
}

Chris Allegretta's avatar
Chris Allegretta committed
1926
#ifndef DISABLE_TABCOMP
1927
1928
1929
/* 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
1930
int append_slash_if_dir(char *buf, int *lastwastab, int *place)
1931
{
1932
    char *dirptr = real_dir_from_tilde(buf);
1933
    struct stat fileinfo;
1934
    int ret = 0;
1935

1936
    assert(dirptr != buf);
1937

1938
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
1939
	strncat(buf, "/", 1);
1940
	(*place)++;
1941
	/* now we start over again with # of tabs so far */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1942
	*lastwastab = 0;
1943
	ret = 1;
1944
1945
    }

1946
    free(dirptr);
1947
    return ret;
1948
}
Chris Allegretta's avatar
Chris Allegretta committed
1949
1950

/*
Chris Allegretta's avatar
Chris Allegretta committed
1951
1952
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1953
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
 *
 * 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)
{
1971
    char **matches = (char **)NULL;
1972
    char *matchline = NULL;
1973
    struct passwd *userdata;
1974

1975
    *num_matches = 0;
1976
    matches = nmalloc(BUFSIZ * sizeof(char *));
1977

1978
    strcat(buf, "*");
1979

1980
    while ((userdata = getpwent()) != NULL) {
1981

1982
	if (check_wildcard_match(userdata->pw_name, &buf[1]) == TRUE) {
1983
1984
1985
1986

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

1988
1989
1990
1991
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

1992
1993
	    if (operating_dir != NULL) {
		if (check_operating_dir(userdata->pw_dir, 1) != 0)
1994
1995
1996
1997
		    continue;
	    }
#endif

1998
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
1999
2000
2001
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
	    ++*num_matches;
2002

2003
2004
2005
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2006
	}
2007
2008
    }
    endpwent();
2009

2010
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2011
2012
2013
2014
2015
2016
2017
}

/* 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
2018
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2019
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2020
2021
2022
    DIR *dir;
    struct dirent *next;

2023
    matches = nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2024
2025
2026
2027

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

2028
    /* Okie, if there's a / in the buffer, strip out the directory part */
2029
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2030
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2031
2032
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2033
	    tmp--;
2034

Chris Allegretta's avatar
Chris Allegretta committed
2035
2036
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2037
2038
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2039

Chris Allegretta's avatar
Chris Allegretta committed
2040
    } else {
2041
2042

#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2043
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
2044
#else
2045
	/* The better, but apparently segfault-causing way */
Chris Allegretta's avatar
Chris Allegretta committed
2046
	if ((dirname = getcwd(NULL, 0)) == NULL)
2047
#endif /* PATH_MAX */
Chris Allegretta's avatar
Chris Allegretta committed
2048
2049
2050
2051
2052
2053
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2054
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2055
2056
2057
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2058

Chris Allegretta's avatar
Chris Allegretta committed
2059
2060
2061
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2062
2063

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2064
    fprintf(stderr, "\nDir = %s\n", dirname);
2065
2066
2067
2068
2069
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2070
    dir = opendir(dirname);
2071
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2072
2073
2074
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2075
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2076
2077
2078
2079
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2080
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2081
2082
2083
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
2084
2085
2086
2087

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2088
2089
2090
2091
2092
2093
2094
2095

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

2096
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2097
2098
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
		if (check_operating_dir(tmp2, 1)) {
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2109
	    tmp2 = NULL;
2110
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2111
2112
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2113
	    ++*num_matches;
2114
2115
2116
2117

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2118
2119
	}
    }
2120
2121
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2122

2123
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2124
2125
}

Chris Allegretta's avatar
Chris Allegretta committed
2126
2127
/* 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
2128
char *input_tab(char *buf, int place, int *lastwastab, int *newplace, int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2129
2130
{
    /* Do TAB completion */
2131
    static int num_matches = 0, match_matches = 0;
2132
    static char **matches = (char **)NULL;
2133
    int pos = place, i = 0, col = 0, editline = 0;
2134
    int longestname = 0, is_dir = 0;
2135
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2136

2137
2138
    *list = 0;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2139
    if (*lastwastab == FALSE) {
2140
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2141

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2142
	*lastwastab = 1;
2143

Chris Allegretta's avatar
Chris Allegretta committed
2144
2145
	/* Make a local copy of the string -- up to the position of the
	   cursor */
2146
2147
	matchbuf = (char *)nmalloc((strlen(buf) + 2) * sizeof(char));
	memset(matchbuf, '\0', (strlen(buf) + 2));
2148

2149
2150
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2151
2152

	/* skip any leading white space */
2153
	while (*tmp && isspace((int)*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2154
2155
2156
2157
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2158
2159
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2160
	    free(matches);
2161
	    matches = (char **)NULL;
2162
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2163
2164
2165
2166
2167
	}

	/* 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
2168
2169
2170
2171
2172
	/* 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. */
2173
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2174
	    buf = mallocstrcpy(buf, tmp);
2175
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2176
	}
2177
2178
2179
2180
2181
2182
	/* 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
2183
2184
2185

	/* Try to match everything in the current working directory that
	 * matches.  */
2186
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2187
2188
2189
	    matches = cwd_tab_completion(tmp, &num_matches);

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

2192
2193
2194
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2195
	/* Did we find exactly one match? */
2196
	switch (num_matches) {
2197
	case 0:
2198
	    blank_edit();
2199
	    wrefresh(edit);
2200
2201
	    break;
	case 1:
2202
2203
2204

	    buf = nrealloc(buf, strlen(buf) + strlen(matches[0]) + 1);

2205
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2206
2207
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2208
		tmp++;
2209
	    } else
2210
2211
		tmp = buf;

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

2215
	    if (is_dir != 0)
2216
		break;
2217
2218

	    copyto = tmp;
2219
2220
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2221
2222
		tmp++;

2223
	    /* write out the matched name */
2224
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2225
2226
	    *newplace += strlen(matches[0]) - pos;

2227
2228
2229
2230
2231
2232
2233
	    /* 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;

2234
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2235
	    append_slash_if_dir(buf, lastwastab, newplace);
2236

2237
2238
	    break;
	default:
2239
	    /* Check to see if all matches share a beginning, and, if so,
2240
	       tack it onto buf and then beep */
2241

2242
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2243
2244
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2245
		tmp++;
2246
	    } else
2247
2248
		tmp = buf;

2249
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2250
		 pos <= strlen(matches[0]); pos++)
2251
2252
		tmp++;

2253
2254
2255
2256
2257
2258
2259
2260
2261
	    while (1) {
		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++;
		}
2262
2263
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2264
		    /* All the matches have the same character at pos+1,
2265
		       so paste it into buf... */
2266
		    buf = nrealloc(buf, strlen(buf) + 2);
2267
		    strncat(buf, matches[0] + pos, 1);
2268
		    *newplace += 1;
2269
		    pos++;
2270
		} else {
2271
2272
2273
2274
		    beep();
		    break;
		}
	    }
2275
	    break;
2276
	}
Chris Allegretta's avatar
Chris Allegretta committed
2277
2278
2279
2280
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2281
	if (matches != NULL && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2282
2283
2284
2285
2286

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

2287
	    editline = 0;
2288

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

2297
	    foo = charalloc(longestname + 5);
2298

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

2302
		/* make each filename shown be the same length as the longest
2303
		   filename, with two spaces at the end */
2304
2305
2306
2307
2308
2309
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2310
2311
		/* Disable el cursor */
		curs_set(0);
2312
2313
2314
2315
2316
2317
		/* 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 */
2318
		if (col > COLS - longestname && i + 1 < num_matches) {
2319
2320
		    editline++;
		    wmove(edit, editline, 0);
2321
2322
2323
2324
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2325
2326
2327
		    col = 0;
		}
	    }
2328
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2329
	    wrefresh(edit);
2330
	    *list = 1;
2331
2332
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2333
2334
    }

2335
2336
2337
2338
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
    if (*list == 0)
	edit_refresh();
2339
2340
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2341
}
2342
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2343

2344
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
2345
/* Return the stat of the file pointed to by path */
Chris Allegretta's avatar
Chris Allegretta committed
2346
2347
struct stat filestat(const char *path)
{
Chris Allegretta's avatar
Chris Allegretta committed
2348
2349
    struct stat st;

2350
    stat(path, &st);
Chris Allegretta's avatar
Chris Allegretta committed
2351
2352
2353
2354
    return st;
}

/* Our sort routine for file listings - sort directories before
2355
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2356
2357
int diralphasort(const void *va, const void *vb)
{
2358
2359
2360
2361
    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
2362

2363
    if (aisdir != 0 && bisdir == 0)
2364
	return -1;
2365
    if (aisdir == 0 && bisdir != 0)
2366
	return 1;
2367

2368
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2369
2370
}

2371
/* Free our malloc()ed memory */
Chris Allegretta's avatar
Chris Allegretta committed
2372
2373
void free_charptrarray(char **array, int len)
{
2374
2375
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2376
2377
2378
    free(array);
}

2379
2380
2381
/* Only print the last part of a path; isn't there a shell 
 * command for this? */
const char *tail(const char *foo)
Chris Allegretta's avatar
Chris Allegretta committed
2382
{
2383
    const char *tmp = foo + strlen(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2384
2385
2386
2387

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

2388
2389
    if (*tmp == '/')
	tmp++;
Chris Allegretta's avatar
Chris Allegretta committed
2390
2391
2392
2393

    return tmp;
}

2394
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2395
2396
void striponedir(char *foo)
{
2397
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2398

2399
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2400
    /* Don't strip the root dir */
2401
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2402
2403
	return;

2404
2405
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2406
    if (*tmp == '/')
2407
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2408
2409
2410
2411
2412

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

    if (tmp != foo)
2413
2414
2415
2416
2417
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2418
    }
Chris Allegretta's avatar
Chris Allegretta committed
2419
2420
}

2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
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;
}

2431
/* Initialize the browser code, including the list of files in *path */
2432
char **browser_init(const char *path, int *longest, int *numents)
2433
2434
2435
{
    DIR *dir;
    struct dirent *next;
2436
    char **filelist;
2437
    int i = 0;
2438
    size_t path_len;
2439
2440

    dir = opendir(path);
2441
    if (dir == NULL)
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
	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;

    filelist = nmalloc(*numents * sizeof (char *));

2457
2458
2459
2460
    if (!strcmp(path, "/"))
	path = "";
    path_len = strlen(path);

2461
2462
2463
2464
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;

2465
2466
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2467
2468
	i++;
    }
2469
    closedir(dir);
2470
2471
2472
2473
2474
2475
2476

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2477
/* Our browser function.  inpath is the path to start browsing from */
2478
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2479
2480
2481
2482
2483
2484
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
    int numents = 0, i = 0, j = 0, kbinput = 0, longest = 0, abort = 0;
    int col = 0, selected = 0, editline = 0, width = 0, filecols = 0;
2485
    int lineno = 0, kb;
2486
2487
    char **filelist = (char **)NULL;
#if !defined(DISABLE_MOUSE) && defined(NCURSES_MOUSE_VERSION)
2488
2489
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2490

2491
2492
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2493
2494
2495
    /* 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
2496
2497
2498
2499
2500
    if (path != NULL && strcmp(path, inpath)) {
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2501
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2502
    if (path == NULL)
2503
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2504
2505

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

2508
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2509
2510
    qsort(filelist, numents, sizeof(char *), diralphasort);

2511
    kb = keypad_on(edit, 1);
Chris Allegretta's avatar
Chris Allegretta committed
2512
    titlebar(path);
2513
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2514
2515
2516
2517
2518
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2519
2520

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2521
    do {
2522
2523
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2524
2525
2526

	blank_statusbar_refresh();

2527
#if !defined(DISABLE_HELP) || (!defined(DISABLE_MOUSE) && defined(NCURSES_MOUSE_VERSION))
2528
	currshortcut = browser_list;
2529
2530
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2531
2532
 	editline = 0;
	col = 0;
2533
	    
2534
	/* Compute line number we're on now, so we don't divide by zero later */
2535
2536
2537
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2538
2539

	switch (kbinput) {
2540

2541
#if !defined(DISABLE_MOUSE) && defined(NCURSES_MOUSE_VERSION)
2542
	case KEY_MOUSE:
2543
	    if (getmouse(&mevent) == ERR)
2544
		return retval;
2545
2546
2547
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2548
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2549
2550
2551
2552
		int selectedbackup = selected;

		mevent.y -= 2;

2553
2554
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2555
		selected = (lineno / editwinrows) * editwinrows * width
2556
2557
2558
2559
2560
2561
			+ 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--;
2562
2563
2564

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2565
		if (selected > numents - 1)
2566
		    selected = numents - 1;
2567
		else if (selectedbackup == selected)
2568
2569
2570
2571
		    ungetch('s');	/* Unget the 'select' key */
	    } else	/* Must be clicking a shortcut */
		do_mouse();

2572
2573
            break;
#endif
2574
	case NANO_UP_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2575
2576
2577
2578
2579
	case KEY_UP:
	case 'u':
	    if (selected - width >= 0)
		selected -= width;
	    break;
2580
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2581
	case KEY_LEFT:
2582
2583
	case NANO_BACKSPACE_KEY:
	case 127:
Chris Allegretta's avatar
Chris Allegretta committed
2584
2585
2586
2587
2588
	case 'l':
	    if (selected > 0)
		selected--;
	    break;
	case KEY_DOWN:
2589
	case NANO_DOWN_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2590
2591
2592
2593
2594
	case 'd':
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
	case KEY_RIGHT:
2595
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2596
2597
2598
2599
2600
	case 'r':
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2601
	case NANO_PREVPAGE_FKEY:
Chris Allegretta's avatar
Chris Allegretta committed
2602
	case KEY_PPAGE:
2603
	case '-':
2604
	    if (selected >= (editwinrows + lineno % editwinrows) * width)
2605
		selected -= (editwinrows + lineno % editwinrows) * width; 
Chris Allegretta's avatar
Chris Allegretta committed
2606
2607
2608
2609
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2610
	case NANO_NEXTPAGE_FKEY:
Chris Allegretta's avatar
Chris Allegretta committed
2611
	case KEY_NPAGE:	
2612
	case ' ':
2613
2614
	    selected += (editwinrows - lineno % editwinrows) * width;
	    if (selected >= numents)
Chris Allegretta's avatar
Chris Allegretta committed
2615
2616
		selected = numents - 1;
	    break;
2617
2618
2619
2620
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
	     do_help();
	     break;
Chris Allegretta's avatar
Chris Allegretta committed
2621
	case KEY_ENTER:
2622
	case NANO_ENTER_KEY:
2623
2624
	case 's': /* More Pico compatibility */
	case 'S':
Chris Allegretta's avatar
Chris Allegretta committed
2625
	    /* You can't cd up from / */
Rocco Corsi's avatar
   
Rocco Corsi committed
2626
	    if (!strcmp(filelist[selected], "/..") && !strcmp(path, "/")) {
Chris Allegretta's avatar
Chris Allegretta committed
2627
		statusbar(_("Can't move up a directory"));
2628
		beep();
Rocco Corsi's avatar
   
Rocco Corsi committed
2629
2630
2631
		break;
	    }

2632
#ifndef DISABLE_OPERATINGDIR
2633
2634
2635
2636
2637
2638
2639
2640
	    /*
	     *  Note: the selected file can be outside the operating
	     *  directory if it is .. or if it is a symlink to a directory
	     *  outside the opdir. */
	    if (check_operating_dir(filelist[selected], FALSE)) {
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		beep();
		break;
2641
2642
2643
	    }
#endif

2644
2645
2646
2647
2648
	    if (stat(filelist[selected], &st) == -1) {
		statusbar(_("Can't open \"%s\": %s"), filelist[selected], strerror(errno));
		beep();
		break;
	    }
2649

2650
2651
2652
2653
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2654
2655
	    }

2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
	    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
2666
		}
2667
2668
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2669

2670
2671
2672
2673
2674
	    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
2675
	    }
2676
2677
2678
2679
2680
2681
2682

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

Rocco Corsi's avatar
   
Rocco Corsi committed
2683
2684
2685
2686
2687
2688
	/* Goto a specific directory */
	case 'g':	/* Pico compatibility */
	case 'G':
	case NANO_GOTO_KEY:

	    curs_set(1);
Chris Allegretta's avatar
Chris Allegretta committed
2689
2690
2691
2692
2693
	    j = statusq(0, gotodir_list, "",
#ifndef NANO_SMALL
		0,
#endif
		_("Goto Directory"));
2694
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2695
2696
2697
2698
2699
2700
2701
	    curs_set(0);

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

2702
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2703

2704
2705
2706
	    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
2707
2708
	    }

2709
2710
2711
2712
2713
2714
2715
2716
2717
#ifndef DISABLE_OPERATINGDIR
	    if (check_operating_dir(new_path, FALSE)) {
		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
2718
2719
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2720
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2721
		break;
2722
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2723
2724

	    /* Start over again with the new path value */
2725
2726
	    free_charptrarray(filelist, numents);
	    free(foo);
2727
2728
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2729
2730
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2731
2732
2733
2734
2735
	/* Stuff we want to abort the browser */
	case 'q':
	case 'Q':
	case 'e':	/* Pico compatibility, yeech */
	case 'E':
2736
	case NANO_CANCEL_KEY:
2737
	case NANO_EXIT_FKEY:
2738
2739
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2740
2741
2742
2743
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2744
2745
	blank_edit();

2746
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
	    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 */
2761
2762
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2763
2764
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2765
2766
2767
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2768
2769
2770
2771
2772
2773
2774
2775
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
		    st = filestat(filelist[j]);
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2776
2777
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2778
			(int) st.st_size);
2779
2780
2781
2782
2783
2784
		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);
2785
		else /* It's more than 1 k and less than a meg */
2786
2787
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2788
2789
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2790
	    /* Highlight the currently selected file/dir */
2791
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2792
		wattron(edit, A_REVERSE);
2793
2794
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2795
2796
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2797
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2798
2799
2800
2801
2802
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2803
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
 	wrefresh(edit);
    } while ((kbinput = wgetch(edit)) != NANO_EXIT_KEY);
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2815
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2816
    edit_refresh();
2817
    kb = keypad_on(edit, kb);
Chris Allegretta's avatar
Chris Allegretta committed
2818

Chris Allegretta's avatar
Chris Allegretta committed
2819
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2820
2821
2822
2823
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2824

2825
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2826
 starts do_browser from there, else from the current dir */
2827
char *do_browse_from(const char *inpath)
2828
2829
{
    struct stat st;
2830
    char *bob;
2831
2832
2833
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2834

2835
2836
2837
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2838

2839
2840
2841
2842
2843
2844
2845
2846
    /*
     * 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);
2847
2848
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2849
	    path = getcwd(NULL, PATH_MAX + 1);
2850
	}
2851
    }
2852

2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
    if (check_operating_dir(path, FALSE))
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2865
    return bob;
2866
}
Chris Allegretta's avatar
Chris Allegretta committed
2867
#endif /* !DISABLE_BROWSER */
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944

#ifndef NANO_SMALL
#ifdef ENABLE_NANORC
void load_history(void)
{
    FILE *hist;
    const struct passwd *userage;
    uid_t euid = geteuid();
    static char *nanohist;
    char *buf, *ptr;
    historyheadtype *history = &search_history;

    do {
	userage = getpwent();
    } while (userage != NULL && userage->pw_uid != euid);
    endpwent();

    /* assume do_rcfile has reported missing home dir */

    if (userage != NULL) {
        nanohist = nrealloc(nanohist, strlen(userage->pw_dir) + 15);
        sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
	hist = fopen(nanohist, "r");
	if (!hist) {
            if (errno != ENOENT)
		rcfile_error(_("Unable to open ~/.nano_history file, %s"), strerror(errno));
	    free(nanohist);
	} else {
	    buf = charalloc(1024);
	    while (fgets(buf, 1023, hist) != 0) {
		ptr = buf;
		while (*ptr != '\n')
		    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;
    const struct passwd *userage;
    uid_t euid = geteuid();
    char *nanohist = NULL;
    historytype *h;

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

    do {
	userage = getpwent();
    } while (userage != NULL && userage->pw_uid != euid);
    endpwent();

    if (userage != NULL) {
	nanohist = nrealloc(nanohist, strlen(userage->pw_dir) + 15);
	sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
	hist = fopen(nanohist, "wb");
	if (!hist) {
	    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) {
2945
		h->data = nrealloc(h->data, strlen(h->data) + 2);
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
		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) {
2957
		h->data = nrealloc(h->data, strlen(h->data) + 2);
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
		    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
		    goto come_from;
		}
	    }
come_from:
	    fclose(hist);
	}
	free(nanohist);
    }
}
#endif /* ENABLE_NANORC */
Chris Allegretta's avatar
Chris Allegretta committed
2971
#endif /* !NANO_SMALL */