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

22
23
#include "config.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return 1;
}

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

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

    return 1;
}

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

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

Chris Allegretta's avatar
Chris Allegretta committed
409
    while (1) {
410
411

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

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

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

    return buf;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

546
#ifndef NANO_SMALL
547
548
	if (i == NANO_EXTCMD_KEY) {
	    realname = mallocstrcpy(realname, "");
549
	    i = open_pipe(answer);
550
	} else
551
#endif /* NANO_SMALL */
552
553
	{
	    realname = real_dir_from_tilde(answer);
554
	    i = open_file(realname, 1, loading_file);
555
	}
556

557
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
558
559
560
561
562
563
564
565
566
567
568
569
570
571
	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);
	}
572
573
#endif

574
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
575

Chris Allegretta's avatar
Chris Allegretta committed
576
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
577
	dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
578
#endif
579

580
#ifdef ENABLE_MULTIBUFFER
581
	if (loading_file)
582
	    load_file(0);
583
584
585
	else
#endif
	    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
586
587

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

590
#ifdef ENABLE_MULTIBUFFER
591
592
593
594
595
596
597
598
599
600
	/* 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
601
602
603
604
605
606
607
608
609
610
611
#ifdef ENABLE_MULTIBUFFER
	if (!loading_file) {
#endif

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

#ifdef ENABLE_MULTIBUFFER
	}
#endif

612
	/* If we've gone off the bottom, recenter; otherwise, just redraw */
Chris Allegretta's avatar
Chris Allegretta committed
613
	if (current->lineno > editbot->lineno)
614
	    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
615
616
617
618
619
	else
	    edit_refresh();

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

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

627
628
629
630
631
    free(inspath);
    inspath = NULL;

    display_main_list();
    return i;
Chris Allegretta's avatar
Chris Allegretta committed
632
633
}

634
635
636
int do_insertfile_void(void)
{
    int result = 0;
637
#ifdef ENABLE_MULTIBUFFER
638
639
640
641
642
643
644
645
    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));
646
647
648
649
650
651
652
653
#else
    result = do_insertfile(0);
#endif

    display_main_list();
    return result;
}

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

    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
714
	    fprintf(stderr, "%s: free'd a node, YAY!\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
715
716
717
718
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
719
	fprintf(stderr, "%s: free'd last node.\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
720
721
722
723
#endif
    }
}

724
/*
725
 * Add/update an entry to the open_files openfilestruct.  If update is
726
 * zero, a new entry is created; otherwise, the current entry is updated.
727
 * Return 0 on success or 1 on error.
728
 */
729
int add_open_file(int update)
730
{
731
    openfilestruct *tmp;
732

733
    if (fileage == NULL || current == NULL || filename == NULL)
734
735
736
	return 1;

    /* if no entries, make the first one */
737
    if (open_files == NULL)
738
	open_files = make_new_opennode(NULL);
739
740
741
742
743
744
745

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

749
750
	tmp = make_new_opennode(NULL);
	splice_opennode(open_files, tmp, open_files->next);
751
752
753
754
	open_files = open_files->next;
    }

    /* save current filename */
755
    open_files->filename = mallocstrcpy(open_files->filename, filename);
756

757
758
759
760
761
#ifndef NANO_SMALL
    /* save the file's stat */
    open_files->originalfilestat = originalfilestat;
#endif

762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
    /* 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 */
778
    open_files->file_lineno = current->lineno;
779

780
781
782
783
    /* start with default modification status: unmodified (and marking
       status, if available: unmarked) */
    open_files->file_flags = 0;

Chris Allegretta's avatar
Chris Allegretta committed
784
785
786
787
    /* if we're updating, save current modification status (and marking
       status, if available) */
    if (update) {
#ifndef NANO_SMALL
788
789
	if (ISSET(MODIFIED))
	    open_files->file_flags |= MODIFIED;
Chris Allegretta's avatar
Chris Allegretta committed
790
791
792
	if (ISSET(MARK_ISSET)) {
	    open_files->file_mark_beginbuf = mark_beginbuf;
	    open_files->file_mark_beginx = mark_beginx;
793
	    open_files->file_flags |= MARK_ISSET;
Chris Allegretta's avatar
Chris Allegretta committed
794
795
	}
#else
796
797
	if (ISSET(MODIFIED))
	    open_files->file_flags |= MODIFIED;
Chris Allegretta's avatar
Chris Allegretta committed
798
799
800
#endif
    }

801
802
803
    /* 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
804
805
806
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
807
    }
808
809

#ifdef DEBUG
810
    fprintf(stderr, "filename is %s\n", open_files->filename);
811
812
813
814
815
816
817
818
819
820
821
822
#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)
{
823
    if (open_files == NULL)
824
825
826
827
	return 1;

    /* set up the filename, the file buffer, the total number of lines in
       the file, and the total file size */
828
    filename = mallocstrcpy(filename, open_files->filename);
829
830
831
#ifndef NANO_SMALL
    originalfilestat = open_files->originalfilestat;
#endif
832
    fileage = open_files->fileage;
833
    current = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
834
    filebot = open_files->filebot;
835
836
837
    totlines = open_files->file_totlines;
    totsize = open_files->file_totsize;

Chris Allegretta's avatar
Chris Allegretta committed
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
    /* 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
853

Chris Allegretta's avatar
Chris Allegretta committed
854
855
856
857
#ifdef ENABLE_COLOR
    update_color();
#endif

858
859
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
860
    do_gotopos(open_files->file_lineno, open_files->file_current_x, open_files->file_current_y, open_files->file_placewewant);
861

Chris Allegretta's avatar
Chris Allegretta committed
862
    /* update the titlebar */
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
    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)
{
878
    if (open_files == NULL)
879
880
881
	return 1;

    /* if we're not about to close the current entry, update it before
882
       doing anything */
883
    if (!closing_file)
884
	add_open_file(1);
885

886
    if (open_files->prev == NULL && open_files->next == NULL) {
887
888
889

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
890
	    statusbar(_("No more open file buffers"));
891
892
893
	return 1;
    }

894
    if (open_files->prev != NULL) {
895
896
897
	open_files = open_files->prev;

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

    }

903
    else if (open_files->next != NULL) {
904
905

	/* if we're at the beginning, wrap around to the end */
906
	while (open_files->next != NULL)
907
908
909
	    open_files = open_files->next;

#ifdef DEBUG
910
	    fprintf(stderr, "filename is %s\n", open_files->filename);
911
912
913
914
915
916
#endif

    }

    load_open_file();

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

920
921
922
923
924
925
926
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

927
928
929
/* This function is used by the shortcut list. */
int open_prevfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
930
    return open_prevfile(0);
931
932
}

933
934
935
936
937
938
939
940
/*
 * 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)
{
941
    if (open_files == NULL)
942
943
944
	return 1;

    /* if we're not about to close the current entry, update it before
945
       doing anything */
946
    if (!closing_file)
947
	add_open_file(1);
948

949
    if (open_files->prev == NULL && open_files->next == NULL) {
950
951
952

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
953
	    statusbar(_("No more open file buffers"));
954
955
956
	return 1;
    }

957
    if (open_files->next != NULL) {
958
959
960
	open_files = open_files->next;

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

    }
965
    else if (open_files->prev != NULL) {
966
967

	/* if we're at the end, wrap around to the beginning */
968
	while (open_files->prev != NULL) {
969
970
971
	    open_files = open_files->prev;

#ifdef DEBUG
972
	    fprintf(stderr, "filename is %s\n", open_files->filename);
973
974
975
976
977
978
979
#endif

	}
    }

    load_open_file();

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

983
984
985
986
987
988
989
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

990
991
992
/* This function is used by the shortcut list. */
int open_nextfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
993
    return open_nextfile(0);
994
995
}

996
997
/*
 * Delete an entry from the open_files filestruct.  After deletion of an
998
 * entry, the next or previous entry is opened, whichever is found first.
999
1000
1001
1002
 * Return 0 on success or 1 on error.
 */
int close_open_file(void)
{
1003
    openfilestruct *tmp;
1004

1005
    if (open_files == NULL)
1006
1007
	return 1;

Chris Allegretta's avatar
Chris Allegretta committed
1008
1009
1010
    /* 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 */
1011
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
1012
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1013

1014
    tmp = open_files;
1015
1016
    if (open_nextfile(1)) {
	if (open_prevfile(1))
1017
1018
1019
	    return 1;
    }

1020
1021
    unlink_opennode(tmp);
    delete_opennode(tmp);
1022
1023
1024
1025
1026

    shortcut_init(0);
    display_main_list();
    return 0;
}
1027
#endif /* MULTIBUFFER */
1028

1029
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
1030
/*
1031
1032
1033
1034
1035
1036
 * 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).
1037
 */
1038
char *get_full_path(const char *origpath)
1039
{
1040
1041
1042
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
1043
    char *expanded_origpath;
1044

1045
    /* first, get the current directory, and tack a slash onto the end of
1046
       it, unless it turns out to be "/", in which case leave it alone */
1047
1048
1049
1050
1051
1052
1053

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

1054
    if (d_here != NULL) {
1055
1056

	align(&d_here);
1057
	if (strcmp(d_here, "/")) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1058
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1059
1060
	    strcat(d_here, "/");
	}
1061
1062
1063
1064
1065

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

1068
	expanded_origpath = real_dir_from_tilde(origpath);
1069
	/* save the value of origpath in both d_there and d_there_file */
1070
1071
1072
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1073

1074
1075
1076
1077
1078
1079
	/* if we have a path but no filename, tack slashes onto the ends
	   of both d_there and d_there_file, if they don't end in slashes
	   already */
	if (path_only) {
	    tmp = d_there[strlen(d_there) - 1];
	    if (tmp != '/') {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1080
		d_there = charealloc(d_there, strlen(d_there) + 2);
1081
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1082
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1083
1084
1085
1086
		strcat(d_there_file, "/");
	    }
	}

1087
1088
1089
1090
1091
	/* 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 */
1092
	if (last_slash == NULL)
1093
	    d_there = mallocstrcpy(d_there, d_here);
1094
	else {
1095
1096
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1097
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1098
	    null_at(&d_there, last_slash_index + 1);
1099
1100
1101

	    /* and remove all non-file elements from d_there_file (i. e.
	       everything before and including the last slash); if we
1102
	       have a path but no filename, don't do anything */
1103
1104
1105
1106
1107
1108
	    if (!path_only) {
		last_slash = strrchr(d_there_file, '/');
		last_slash++;
		strcpy(d_there_file, last_slash);
		align(&d_there_file);
	    }
1109
1110
1111

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1112
1113
1114
		/* 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 */
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124

		free(d_there);

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

		align(&d_there);
1125
		if (d_there != NULL) {
1126
1127
1128
1129

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
		    if (strcmp(d_there, "/")) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1130
			d_there = charealloc(d_there, strlen(d_there) + 2);
1131
1132
			strcat(d_there, "/");
		    }
1133
1134
1135
		}
		else
		    return NULL;
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
	    }

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

1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
	/* 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);
	}
1157
1158
1159
1160
1161
1162
1163
1164
1165

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

    return newpath;
}
1166
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1167
1168
1169

#ifndef DISABLE_SPELLER
/*
1170
1171
1172
 * 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.
1173
 */
1174
char *check_writable_directory(const char *path)
1175
{
1176
    char *full_path = get_full_path(path);
1177
    int writable;
1178
1179
    struct stat fileinfo;

1180
    /* if get_full_path() failed, return NULL */
1181
    if (full_path == NULL)
1182
	return NULL;
1183
1184
1185

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

    /* if the full path doesn't end in a slash (meaning get_full_path()
1189
1190
1191
1192
       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);
1193
	return NULL;
1194
    }
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204

    /* 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
1205
1206
1207
1208
1209
1210
1211
 * 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.
1212
 */
1213
1214
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1215
1216
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1217
    int filedesc;
1218

1219
1220
      /* 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,
1221
         leave full_tempdir set to NULL */
1222
    TMPDIR_env = getenv("TMPDIR");
1223
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1224
	full_tempdir = check_writable_directory(TMPDIR_env);
1225

1226
1227
1228
    /* 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 */
1229
    if (full_tempdir == NULL && dirname != NULL)
1230
	full_tempdir = check_writable_directory(dirname);
1231
1232
1233

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1234
    if (full_tempdir == NULL)
1235
	full_tempdir = check_writable_directory(P_tmpdir);
1236
1237

    /* if P_tmpdir didn't work, use /tmp instead */
1238
    if (full_tempdir == NULL) {
1239
1240
1241
1242
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1243
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1244
1245

    /* like tempnam(), use only the first 5 characters of the prefix */
1246
1247
1248
1249
1250
1251
1252
1253
    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) {
1254
	close(filedesc);
1255
1256
	unlink(full_tempdir);
	return full_tempdir;
1257
    }
1258
1259
1260

    free(full_tempdir);
    return NULL;
1261
1262
}
#endif /* !DISABLE_SPELLER */
1263
1264

#ifndef DISABLE_OPERATINGDIR
1265
1266
1267
1268
1269
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1270
    if (operating_dir == NULL)
1271
1272
1273
1274
1275
	return;
    full_operating_dir = get_full_path(operating_dir);

    /* If get_full_path() failed or the operating directory is
       inaccessible, unset operating_dir. */
1276
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1277
1278
1279
1280
1281
1282
1283
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1284
/* Check to see if we're inside the operating directory.  Return 0 if we
1285
1286
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1287
 * completion will work. */
1288
int check_operating_dir(const char *currpath, int allow_tabcomp)
1289
{
1290
1291
1292
1293
    /* The char *full_operating_dir is global for mem cleanup.  It
     * should have already been initialized by init_operating_dir().
     * Also, a relative operating directory path will only be handled
     * properly if this is done. */
1294

1295
1296
1297
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1298

1299
    /* If no operating directory is set, don't bother doing anything. */
1300
    if (operating_dir == NULL)
1301
	return 0;
1302
    assert(full_operating_dir != NULL);
1303
1304

    fullpath = get_full_path(currpath);
1305
1306
1307
1308
1309
1310
1311
1312

    /* fullpath == NULL means some directory in the path doesn't exist
     * or is unreadable.  If allow_tabcomp is zero, then currpath is
     * what the user typed somewhere.  We don't want to report a
     * non-existent directory as being outside the operating directory,
     * so we return 0.  If allow_tabcomp is nonzero, then currpath
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1313
    if (fullpath == NULL)
1314
	return allow_tabcomp;
1315
1316
1317
1318
1319

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

1320
1321
1322
1323
1324
    /* If both searches failed, we're outside the operating directory.
     * Otherwise, check the search results; if the full operating
     * directory path is not at the beginning of the full current path
     * (for normal usage) and vice versa (for tab completion, if we're
     * allowing it), we're outside the operating directory. */
1325
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1326
1327
	retval = 1;
    free(fullpath);	
1328
1329

    /* Otherwise, we're still inside it. */
1330
    return retval;
1331
}
1332
1333
#endif

1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
/* Read from inn, write to out.  We assume inn is opened for reading,
 * and out for writing.  We return 0 on success, -1 on read error, -2 on
 * write error. */
int copy_file(FILE *inn, FILE *out)
{
    char buf[BUFSIZ];
    size_t charsread;
    int retval = 0;

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

/* Write a file out.  If tmp is nonzero, we set the umask to disallow
 * anyone else from accessing the file, we don't set the global variable
 * filename to its name, and we don't print out how many lines we wrote
 * on the statusbar.
Chris Allegretta's avatar
Chris Allegretta committed
1366
 *
1367
1368
 * tmp means we are writing a temporary file in a secure fashion.  We
 * use it when spell checking or dumping the file on an error.
1369
 *
1370
1371
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1372
1373
 *
 * nonamechange means don't change the current filename, it is ignored
1374
 * if tmp is nonzero or if we're appending/prepending.
1375
1376
 *
 * Return -1 on error, 1 on success. */
1377
int write_file(const char *name, int tmp, int append, int nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1378
{
1379
1380
    int retval = -1;
	/* Instead of returning in this function, you should always
1381
1382
1383
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
    const filestruct *fileptr = fileage;
1384
    int fd;
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
    mode_t original_umask = 0;
	/* Our umask, from when nano started. */
    int realexists;
	/* The result of stat().  True if the file exists, false
	 * otherwise.  If name is a link that points nowhere, realexists
	 * is false. */
    struct stat st;
	/* The status fields filled in by stat(). */
    int anyexists;
	/* Result of lstat().  Same as realexists unless name is a
	 * link. */
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
	/* name after ~ expansion. */
    FILE *f;
	/* The actual file, realname, we are writing to. */
    char *tempname = NULL;
	/* The temp file name we write to on prepend. */
Chris Allegretta's avatar
Chris Allegretta committed
1404

1405
    assert(name != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1406
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1407
1408
1409
	statusbar(_("Cancelled"));
	return -1;
    }
1410
1411
    if (!tmp)
	titlebar(NULL);
1412

1413
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1414

1415
#ifndef DISABLE_OPERATINGDIR
1416
    /* If we're writing a temporary file, we're probably going outside
1417
1418
     * the operating directory, so skip the operating directory test. */
    if (!tmp && check_operating_dir(realname, 0) != 0) {
1419
1420
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1421
1422
1423
    }
#endif

1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
    anyexists = lstat(realname, &lst) != -1;
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
    /* If NOFOLLOW_SYMLINKS is set, it doesn't make sense to prepend or
     * append to a symlink.  Here we warn about the contradiction. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode)) {
	statusbar(_("Cannot prepend or append to a symlink with --nofollow set."));
	goto cleanup_and_exit;
    }

1435
    /* Save the state of file at the end of the symlink (if there is
1436
1437
     * one). */
    realexists = stat(realname, &st) != -1;
1438

1439
1440
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1441
1442
1443
1444
1445
1446
1447
1448
     * temporary, and the file already exists.  Furthermore, if we
     * aren't appending, prepending, or writing a selection, we backup
     * only if the file has not been modified by someone else since nano
     * opened it. */
    if (ISSET(BACKUP_FILE) && !tmp && realexists != 0 &&
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1449
	FILE *backup_file;
1450
	char *backupname;
1451
	struct utimbuf filetime;
1452
	int copy_status;
1453

1454
	/* Save the original file's access and modification times. */
1455
1456
1457
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1458
	/* Open the original file to copy to the backup. */
1459
	f = fopen(realname, "rb");
1460
	if (f == NULL) {
1461
	    statusbar(_("Error reading %s: %s"), realname,
1462
		strerror(errno));
1463
	    goto cleanup_and_exit;
1464
1465
1466
1467
1468
	}

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

1469
1470
1471
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1472
	backup_file = fopen(backupname, "wb");
1473
1474
1475
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
	    statusbar(_("Error writing %s: %s"), backupname, strerror(errno));
1476
	    free(backupname);
1477
1478
1479
1480
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1481
1482
1483
	}

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

1487
1488
1489
1490
1491
1492
1493
1494
1495
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
	/* And set metadata. */
	if (copy_status != 0 || chown(backupname, originalfilestat.st_uid,
		originalfilestat.st_gid) == -1 ||
		utime(backupname, &filetime) == -1) {
	    free(backupname);
	    if (copy_status == -1)
		statusbar(_("Error reading %s: %s"), realname,
1496
			strerror(errno));
1497
1498
1499
1500
1501
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1502
1503
	free(backupname);
    }
1504
#endif /* !NANO_SMALL */
1505

1506
1507
1508
1509
1510
1511
    /* If NOFOLLOW_SYMLINKS and the file is a link, we aren't doing
     * prepend or append.  So we delete the link first, and just
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1512
	goto cleanup_and_exit;
1513
    }
1514

1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
    original_umask = umask(0);
    umask(original_umask);
    /* If we create a temp file, we don't let anyone else access it.  We
     * create a temp file if tmp is nonzero or if we prepend. */
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

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

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

1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
	fd_source = open(realname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
	}
	if (f_source == NULL) {
	    statusbar(_("Error reading %s: %s"), realname, strerror(errno));
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

	if (copy_file(f_source, f) != 0) {
	    statusbar(_("Error writing %s: %s"), tempname, strerror(errno));
	    unlink(tempname);
1559
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1560
1561
1562
	}
    }

1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
    /* Now open the file in place.  Use O_EXCL if tmp is nonzero.  This
     * is now copied from joe, because wiggy says so *shrug*. */
    fd = open(realname, O_WRONLY | O_CREAT |
	(append == 1 ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
	S_IRUSR | S_IWUSR);

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1579
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1580
    if (f == NULL) {
1581
1582
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1583
	goto cleanup_and_exit;
1584
1585
    }

1586
1587
1588
1589
1590
1591
    /* There might not be a magic line.  There won't be when writing out
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1592

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

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

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

1601
	if (size < data_len) {
1602
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1603
	    fclose(f);
1604
	    goto cleanup_and_exit;
1605
	}
1606
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1607
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1608
1609
1610
1611
1612
	    if (putc('\r', f) == EOF) {
		statusbar(_("Error writing %s: %s"), realname, strerror(errno));
		fclose(f);
		goto cleanup_and_exit;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1613
1614

	if (!ISSET(MAC_FILE))
1615
#endif
1616
1617
1618
1619
1620
	    if (putc('\n', f) == EOF) {
		statusbar(_("Error writing %s: %s"), realname, strerror(errno));
		fclose(f);
		goto cleanup_and_exit;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1621
1622
1623
1624
1625

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

1626
    /* If we're prepending, open the temp file, and append it to f. */
1627
    if (append == 2) {
1628
1629
1630
1631
1632
1633
1634
1635
	int fd_source;
	FILE *f_source = NULL;

	fd_source = open(tempname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1636
	}
1637
	if (f_source == NULL) {
1638
1639
	    statusbar(_("Error reading %s: %s"), tempname, strerror(errno));
	    fclose(f);
1640
	    goto cleanup_and_exit;
1641
1642
	}

1643
1644
1645
	if (copy_file(f_source, f) == -1
		|| unlink(tempname) == -1) {
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1646
	    goto cleanup_and_exit;
1647
	}
1648
1649
1650
1651
    } else if (fclose(f) == EOF) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	unlink(tempname);
	goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1652
    }
1653

1654
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1655
	if (!nonamechange) {
1656
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1657
1658
#ifdef ENABLE_COLOR
	    update_color();
1659
1660
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1661
1662
#endif
	}
1663

1664
1665
1666
1667
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1668
1669
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1670
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1671
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1672
    }
1673
1674
1675
1676
1677

    retval = 1;

  cleanup_and_exit:
    free(realname);
1678
    free(tempname);
1679
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1680
1681
}

1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
#ifndef NANO_SMALL
/* Write a marked selection from a file out.  First, set fileage and
 * filebot as the top and bottom of the mark, respectively.  Then call
 * write_file() with the values of name, temp, append, and nonamechange.
 * Finally, set fileage and filebot back to their old values and
 * return. */
int write_marked(const char *name, int tmp, int append, int
	nonamechange)
{
    int retval = -1;
    filestruct *fileagebak = fileage;
    filestruct *filebotbak = filebot;
    int oldmod = ISSET(MODIFIED);
	/* write_file() unsets the MODIFIED flag. */
    size_t topx;
	/* The column of the beginning of the mark. */
    char origchar;
	/* We replace the character at the end of the mark with '\0'.
	 * We save the original character, to restore it. */
    char *origcharloc;
	/* The location of the character we nulled. */

    if (!ISSET(MARK_ISSET))
	return -1;

    /* Set fileage as the top of the mark, and filebot as the bottom. */
    if (current->lineno > mark_beginbuf->lineno ||
		(current->lineno == mark_beginbuf->lineno &&
		current_x > mark_beginx)) {
	fileage = mark_beginbuf;
	topx = mark_beginx;
	filebot = current;
	origcharloc = current->data + current_x;
    } else {
	fileage = current;
	topx = current_x;
	filebot = mark_beginbuf;
	origcharloc = mark_beginbuf->data + mark_beginx;
    }
    origchar = *origcharloc;
    *origcharloc = '\0';
    fileage->data += topx;

    /* If the line at filebot is blank, treat it as the magicline and
     * hence the end of the file.  Otherwise, treat the line after
     * filebot as the end of the file. */
    if (filebot->data[0] != '\0' && filebot->next != NULL)
	filebot = filebot->next;

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

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

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

1745
int do_writeout(const char *path, int exiting, int append)
Chris Allegretta's avatar
Chris Allegretta committed
1746
1747
{
    int i = 0;
1748
1749
1750
#ifdef NANO_EXTRA
    static int did_cred = 0;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1751

1752
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1753
    currshortcut = writefile_list;
1754
1755
#endif

1756
    answer = mallocstrcpy(answer, path);
Chris Allegretta's avatar
Chris Allegretta committed
1757

1758
    if (exiting && ISSET(TEMP_OPT)) {
1759
	if (filename[0] != '\0') {
1760
	    i = write_file(answer, 0, 0, 0);
1761
1762
	    display_main_list();
	    return i;
1763
	} else {
Chris Allegretta's avatar
Chris Allegretta committed
1764
1765
1766
1767
1768
	    UNSET(TEMP_OPT);
	    do_exit();

	    /* They cancelled, abort quit */
	    return -1;
1769
	}
Chris Allegretta's avatar
Chris Allegretta committed
1770
1771
1772
    }

    while (1) {
1773
#ifndef NANO_SMALL
1774
1775
	const char *formatstr, *backupstr;

1776
1777
1778
1779
1780
1781
1782
	if (ISSET(MAC_FILE))
	   formatstr = _(" [Mac Format]");
	else if (ISSET(DOS_FILE))
	   formatstr = _(" [DOS Format]");
	else
	   formatstr = "";

1783
1784
1785
1786
1787
	if (ISSET(BACKUP_FILE))
	   backupstr = _(" [Backup]");
	else
	   backupstr = "";

1788
	/* Be nice to the translation folks */
1789
	if (ISSET(MARK_ISSET) && !exiting) {
1790
	    if (append == 2)
1791
		i = statusq(1, writefile_list, "", 0,
1792
		    "%s%s%s", _("Prepend Selection to File"), formatstr, backupstr);
Chris Allegretta's avatar
Chris Allegretta committed
1793
	    else if (append == 1)
1794
		i = statusq(1, writefile_list, "", 0,
1795
		    "%s%s%s", _("Append Selection to File"), formatstr, backupstr);
1796
	    else
1797
		i = statusq(1, writefile_list, "", 0,
1798
1799
		    "%s%s%s", _("Write Selection to File"), formatstr, backupstr);
	} else {
1800
	    if (append == 2)
1801
		i = statusq(1, writefile_list, answer, 0,
1802
		    "%s%s%s", _("File Name to Prepend to"), formatstr, backupstr);
Chris Allegretta's avatar
Chris Allegretta committed
1803
	    else if (append == 1)
1804
		i = statusq(1, writefile_list, answer, 0,
1805
		    "%s%s%s", _("File Name to Append to"), formatstr, backupstr);
1806
	    else
1807
		i = statusq(1, writefile_list, answer, 0,
1808
		    "%s%s%s", _("File Name to Write"), formatstr, backupstr);
1809
	}
1810
1811
#else
	if (append == 2)
Chris Allegretta's avatar
Chris Allegretta committed
1812
	    i = statusq(1, writefile_list, answer,
1813
		"%s", _("File Name to Prepend to"));
Chris Allegretta's avatar
Chris Allegretta committed
1814
1815
	else if (append == 1)
	    i = statusq(1, writefile_list, answer,
1816
1817
		"%s", _("File Name to Append to"));
	else
Chris Allegretta's avatar
Chris Allegretta committed
1818
	    i = statusq(1, writefile_list, answer,
1819
1820
		"%s", _("File Name to Write"));
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1821

1822
1823
1824
1825
1826
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
	    return 0;
	}
Chris Allegretta's avatar
Chris Allegretta committed
1827

1828
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1829
	if (i == NANO_TOFILES_KEY) {
1830
	    char *tmp = do_browse_from(answer);
1831

1832
	    currshortcut = writefile_list;
1833
1834
1835
1836
	    if (tmp == NULL)
		continue;
	    free(answer);
	    answer = tmp;
1837
	} else
1838
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1839
#ifndef NANO_SMALL
1840
1841
1842
	if (i == TOGGLE_DOS_KEY) {
	    UNSET(MAC_FILE);
	    TOGGLE(DOS_FILE);
1843
	    continue;
1844
1845
1846
	} else if (i == TOGGLE_MAC_KEY) {
	    UNSET(DOS_FILE);
	    TOGGLE(MAC_FILE);
1847
	    continue;
1848
1849
	} else if (i == TOGGLE_BACKUP_KEY) {
	    TOGGLE(BACKUP_FILE);
1850
1851
	    continue;
	} else
1852
#endif /* !NANO_SMALL */
1853
1854
1855
1856
1857
1858
1859
	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
1860

Chris Allegretta's avatar
Chris Allegretta committed
1861
#ifdef DEBUG
1862
	fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1863
#endif
1864
1865

#ifdef NANO_EXTRA
1866
	if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy")
1867
		&& !did_cred) {
1868
1869
1870
1871
	    do_credits();
	    did_cred = 1;
	    return -1;
	}
1872
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1873
	if (append == 0 && strcmp(answer, filename)) {
1874
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1875

1876
	    if (!stat(answer, &st)) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1877
		i = do_yesno(0, _("File exists, OVERWRITE ?"));
1878
1879
1880
1881
1882
1883
		if (i == 0 || i == -1)
		    continue;
	    } else if (filename[0] != '\0'
#ifndef NANO_SMALL
		&& (!ISSET(MARK_ISSET) || exiting)
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1884
		) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1885
		i = do_yesno(0, _("Save file under DIFFERENT NAME ?"));
1886
1887
1888
		if (i == 0 || i == -1)
		    continue;
	    }
1889
	}
Chris Allegretta's avatar
Chris Allegretta committed
1890

1891
#ifndef NANO_SMALL
1892
1893
	/* Here's where we allow the selected text to be written to
	 * a separate file. */
1894
1895
1896
	if (ISSET(MARK_ISSET) && !exiting)
	    i = write_marked(answer, 0, append, 1);
	else
1897
#endif /* !NANO_SMALL */
1898
	    i = write_file(answer, 0, append, 0);
1899

1900
#ifdef ENABLE_MULTIBUFFER
1901
1902
1903
1904
	/* If we're not about to exit, update the current entry in
	   the open_files structure. */
	if (!exiting)
	    add_open_file(1);
1905
#endif
1906
1907
1908
	display_main_list();
	return i;
    } /* while (1) */
Chris Allegretta's avatar
Chris Allegretta committed
1909
1910
1911
1912
}

int do_writeout_void(void)
{
1913
    return do_writeout(filename, 0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
1914
}
Chris Allegretta's avatar
Chris Allegretta committed
1915

1916
/* Return a malloc()ed string containing the actual directory, used
1917
1918
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1919
{
Chris Allegretta's avatar
Chris Allegretta committed
1920
    char *dirtmp = NULL;
1921

1922
    if (buf[0] == '~') {
1923
1924
1925
1926
1927
1928
1929
	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++)
	    ;

1930
1931
	/* Determine home directory using getpwuid() or getpwent(),
	   don't rely on $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
1932
1933
1934
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
1935
1936
1937
1938
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
			strncmp(userdata->pw_name, buf + 1, i - 1));
Chris Allegretta's avatar
Chris Allegretta committed
1939
1940
	}
	endpwent();
1941

1942
	if (userdata != NULL) {	/* User found */
1943
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1944
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1945
	}
1946
    }
1947

1948
1949
1950
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1951
    return dirtmp;
1952
1953
}

Chris Allegretta's avatar
Chris Allegretta committed
1954
#ifndef DISABLE_TABCOMP
1955
1956
1957
/* 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
1958
int append_slash_if_dir(char *buf, int *lastwastab, int *place)
1959
{
1960
    char *dirptr = real_dir_from_tilde(buf);
1961
    struct stat fileinfo;
1962
    int ret = 0;
1963

1964
    assert(dirptr != buf);
1965

1966
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
1967
	strncat(buf, "/", 1);
1968
	(*place)++;
1969
	/* now we start over again with # of tabs so far */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1970
	*lastwastab = 0;
1971
	ret = 1;
1972
1973
    }

1974
    free(dirptr);
1975
    return ret;
1976
}
Chris Allegretta's avatar
Chris Allegretta committed
1977
1978

/*
Chris Allegretta's avatar
Chris Allegretta committed
1979
1980
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1981
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
 *
 * 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)
{
1999
    char **matches = (char **)NULL;
2000
    char *matchline = NULL;
2001
    struct passwd *userdata;
2002

2003
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2004
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2005

2006
    strcat(buf, "*");
2007

2008
    while ((userdata = getpwent()) != NULL) {
2009

2010
	if (check_wildcard_match(userdata->pw_name, &buf[1]) == TRUE) {
2011
2012
2013
2014

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

2016
2017
2018
2019
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2020
2021
	    if (operating_dir != NULL) {
		if (check_operating_dir(userdata->pw_dir, 1) != 0)
2022
2023
2024
2025
		    continue;
	    }
#endif

2026
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2027
2028
2029
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
	    ++*num_matches;
2030

2031
2032
2033
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2034
	}
2035
2036
    }
    endpwent();
2037

2038
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2039
2040
2041
2042
2043
2044
2045
}

/* 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
2046
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2047
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2048
2049
2050
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2051
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2052
2053
2054
2055

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

2056
    /* Okie, if there's a / in the buffer, strip out the directory part */
2057
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2058
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2059
2060
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2061
	    tmp--;
2062

Chris Allegretta's avatar
Chris Allegretta committed
2063
2064
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2065
2066
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2067

Chris Allegretta's avatar
Chris Allegretta committed
2068
    } else {
2069
2070

#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2071
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
2072
#else
2073
	/* The better, but apparently segfault-causing way */
Chris Allegretta's avatar
Chris Allegretta committed
2074
	if ((dirname = getcwd(NULL, 0)) == NULL)
2075
#endif /* PATH_MAX */
Chris Allegretta's avatar
Chris Allegretta committed
2076
2077
2078
2079
2080
2081
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2082
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2083
2084
2085
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2086

Chris Allegretta's avatar
Chris Allegretta committed
2087
2088
2089
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2090
2091

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2092
    fprintf(stderr, "\nDir = %s\n", dirname);
2093
2094
2095
2096
2097
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2098
    dir = opendir(dirname);
2099
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2100
2101
2102
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2103
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2104
2105
2106
2107
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2108
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2109
2110
2111
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
2112
2113
2114
2115

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2116
2117
2118
2119
2120
2121
2122
2123

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

2124
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2125
2126
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2127
2128
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2129
		if (check_operating_dir(tmp2, 1) != 0) {
2130
2131
2132
2133
2134
2135
2136
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2137
	    tmp2 = NULL;
2138
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2139
2140
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2141
	    ++*num_matches;
2142
2143
2144
2145

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2146
2147
	}
    }
2148
2149
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2150

2151
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2152
2153
}

Chris Allegretta's avatar
Chris Allegretta committed
2154
2155
/* 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
2156
char *input_tab(char *buf, int place, int *lastwastab, int *newplace, int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2157
2158
{
    /* Do TAB completion */
2159
    static int num_matches = 0, match_matches = 0;
2160
    static char **matches = (char **)NULL;
2161
    int pos = place, i = 0, col = 0, editline = 0;
2162
    int longestname = 0, is_dir = 0;
2163
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2164

2165
2166
    *list = 0;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2167
    if (*lastwastab == FALSE) {
2168
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2169

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2170
	*lastwastab = 1;
2171

Chris Allegretta's avatar
Chris Allegretta committed
2172
2173
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2174
2175
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2176

2177
2178
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2179
2180

	/* skip any leading white space */
2181
	while (*tmp && isspace((int)*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2182
2183
2184
2185
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2186
2187
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2188
	    free(matches);
2189
	    matches = (char **)NULL;
2190
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2191
2192
2193
2194
2195
	}

	/* 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
2196
2197
2198
2199
2200
	/* 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. */
2201
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2202
	    buf = mallocstrcpy(buf, tmp);
2203
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2204
	}
2205
2206
2207
2208
2209
2210
	/* 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
2211
2212
2213

	/* Try to match everything in the current working directory that
	 * matches.  */
2214
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2215
2216
2217
	    matches = cwd_tab_completion(tmp, &num_matches);

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

2220
2221
2222
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2223
	/* Did we find exactly one match? */
2224
	switch (num_matches) {
2225
	case 0:
2226
	    blank_edit();
2227
	    wrefresh(edit);
2228
2229
	    break;
	case 1:
2230

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

2233
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2234
2235
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2236
		tmp++;
2237
	    } else
2238
2239
		tmp = buf;

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

2243
	    if (is_dir != 0)
2244
		break;
2245
2246

	    copyto = tmp;
2247
2248
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2249
2250
		tmp++;

2251
	    /* write out the matched name */
2252
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2253
2254
	    *newplace += strlen(matches[0]) - pos;

2255
2256
2257
2258
2259
2260
2261
	    /* 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;

2262
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2263
	    append_slash_if_dir(buf, lastwastab, newplace);
2264

2265
2266
	    break;
	default:
2267
	    /* Check to see if all matches share a beginning, and, if so,
2268
	       tack it onto buf and then beep */
2269

2270
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2271
2272
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2273
		tmp++;
2274
	    } else
2275
2276
		tmp = buf;

2277
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2278
		 pos <= strlen(matches[0]); pos++)
2279
2280
		tmp++;

2281
2282
2283
2284
2285
2286
2287
2288
2289
	    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++;
		}
2290
2291
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2292
		    /* All the matches have the same character at pos+1,
2293
		       so paste it into buf... */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2294
		    buf = charealloc(buf, strlen(buf) + 2);
2295
		    strncat(buf, matches[0] + pos, 1);
2296
		    *newplace += 1;
2297
		    pos++;
2298
		} else {
2299
2300
2301
2302
2303
		    beep();
		    break;
		}
	    }
	}
Chris Allegretta's avatar
Chris Allegretta committed
2304
2305
2306
2307
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2308
	if (matches != NULL && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2309
2310
2311
2312
2313

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

2314
	    editline = 0;
2315

2316
2317
2318
2319
2320
2321
2322
2323
	    /* 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;

2324
	    foo = charalloc(longestname + 5);
2325

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

2329
		/* make each filename shown be the same length as the longest
2330
		   filename, with two spaces at the end */
2331
2332
2333
2334
2335
2336
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2337
2338
		/* Disable el cursor */
		curs_set(0);
2339
2340
2341
2342
2343
2344
		/* 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 */
2345
		if (col > COLS - longestname && i + 1 < num_matches) {
2346
2347
		    editline++;
		    wmove(edit, editline, 0);
2348
2349
2350
2351
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2352
2353
2354
		    col = 0;
		}
	    }
2355
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2356
	    wrefresh(edit);
2357
	    *list = 1;
2358
2359
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2360
2361
    }

2362
2363
2364
2365
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
    if (*list == 0)
	edit_refresh();
2366
2367
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2368
}
2369
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2370

2371
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
2372
/* Return the stat of the file pointed to by path */
Chris Allegretta's avatar
Chris Allegretta committed
2373
2374
struct stat filestat(const char *path)
{
Chris Allegretta's avatar
Chris Allegretta committed
2375
2376
    struct stat st;

2377
    stat(path, &st);
Chris Allegretta's avatar
Chris Allegretta committed
2378
2379
2380
    return st;
}

2381
/* Our sort routine for file listings -- sort directories before
2382
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2383
2384
int diralphasort(const void *va, const void *vb)
{
2385
2386
2387
2388
    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
2389

2390
    if (aisdir != 0 && bisdir == 0)
2391
	return -1;
2392
    if (aisdir == 0 && bisdir != 0)
2393
	return 1;
2394

2395
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2396
2397
}

2398
/* Free our malloc()ed memory */
Chris Allegretta's avatar
Chris Allegretta committed
2399
2400
void free_charptrarray(char **array, int len)
{
2401
2402
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2403
2404
2405
    free(array);
}

2406
2407
2408
/* 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
2409
{
2410
    const char *tmp = foo + strlen(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2411
2412
2413
2414

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

2415
2416
    if (*tmp == '/')
	tmp++;
Chris Allegretta's avatar
Chris Allegretta committed
2417
2418
2419
2420

    return tmp;
}

2421
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2422
2423
void striponedir(char *foo)
{
2424
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2425

2426
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2427
    /* Don't strip the root dir */
2428
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2429
2430
	return;

2431
2432
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2433
    if (*tmp == '/')
2434
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2435
2436
2437
2438
2439

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

    if (tmp != foo)
2440
2441
2442
2443
2444
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2445
    }
Chris Allegretta's avatar
Chris Allegretta committed
2446
2447
}

2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
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;
}

2458
/* Initialize the browser code, including the list of files in *path */
2459
char **browser_init(const char *path, int *longest, int *numents)
2460
2461
2462
{
    DIR *dir;
    struct dirent *next;
2463
    char **filelist;
2464
    int i = 0;
2465
    size_t path_len;
2466
2467

    dir = opendir(path);
2468
    if (dir == NULL)
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
	return NULL;

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

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

2484
2485
2486
2487
    if (!strcmp(path, "/"))
	path = "";
    path_len = strlen(path);

2488
2489
2490
2491
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;

2492
2493
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2494
2495
	i++;
    }
2496
    closedir(dir);
2497
2498
2499
2500
2501
2502
2503

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2504
/* Our browser function.  inpath is the path to start browsing from */
2505
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2506
2507
2508
2509
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2510
2511
2512
    int numents = 0, i = 0, j = 0, kbinput = -1, meta, longest = 0;
    int abort = 0, col = 0, selected = 0, editline = 0, width = 0;
    int filecols = 0, lineno = 0;
2513
    char **filelist = (char **)NULL;
2514
#ifndef DISABLE_MOUSE
2515
2516
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2517

2518
2519
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2520
2521
2522
    /* 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
2523
2524
2525
2526
2527
    if (path != NULL && strcmp(path, inpath)) {
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2528
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2529
    if (path == NULL)
2530
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2531
2532

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

2535
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2536
2537
2538
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2539
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2540
2541
2542
2543
2544
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2545
2546

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2547
    do {
2548
2549
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2550
2551
2552

	blank_statusbar_refresh();

2553
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2554
	currshortcut = browser_list;
2555
2556
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2557
2558
 	editline = 0;
	col = 0;
2559
	    
2560
	/* Compute line number we're on now, so we don't divide by zero later */
2561
2562
2563
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2564
2565

	switch (kbinput) {
2566

2567
#ifndef DISABLE_MOUSE
2568
	case KEY_MOUSE:
2569
	    if (getmouse(&mevent) == ERR)
2570
		return retval;
2571
2572
2573
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2574
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2575
2576
2577
2578
		int selectedbackup = selected;

		mevent.y -= 2;

2579
2580
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2581
		selected = (lineno / editwinrows) * editwinrows * width
2582
2583
2584
2585
2586
2587
			+ 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--;
2588
2589
2590

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2591
		if (selected > numents - 1)
2592
		    selected = numents - 1;
2593
		else if (selectedbackup == selected)
2594
2595
2596
2597
		    ungetch('s');	/* Unget the 'select' key */
	    } else	/* Must be clicking a shortcut */
		do_mouse();

2598
2599
            break;
#endif
2600
	case NANO_PREVLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2601
2602
2603
	    if (selected - width >= 0)
		selected -= width;
	    break;
2604
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2605
2606
2607
	    if (selected > 0)
		selected--;
	    break;
2608
	case NANO_NEXTLINE_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2609
2610
2611
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
2612
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2613
2614
2615
2616
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2617
	case NANO_PREVPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2618
	case '-': /* Pico compatibility */
2619
	    if (selected >= (editwinrows + lineno % editwinrows) * width)
2620
		selected -= (editwinrows + lineno % editwinrows) * width; 
Chris Allegretta's avatar
Chris Allegretta committed
2621
2622
2623
2624
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2625
	case NANO_NEXTPAGE_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2626
	case ' ': /* Pico compatibility */
2627
2628
	    selected += (editwinrows - lineno % editwinrows) * width;
	    if (selected >= numents)
Chris Allegretta's avatar
Chris Allegretta committed
2629
2630
		selected = numents - 1;
	    break;
2631
2632
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2633
	case '?': /* Pico compatibility */
2634
2635
	     do_help();
	     break;
2636
	case NANO_ENTER_KEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2637
2638
	case 'S': /* Pico compatibility */
	case 's':
Chris Allegretta's avatar
Chris Allegretta committed
2639
	    /* You can't cd up from / */
Rocco Corsi's avatar
   
Rocco Corsi committed
2640
	    if (!strcmp(filelist[selected], "/..") && !strcmp(path, "/")) {
Chris Allegretta's avatar
Chris Allegretta committed
2641
		statusbar(_("Can't move up a directory"));
2642
		beep();
Rocco Corsi's avatar
   
Rocco Corsi committed
2643
2644
2645
		break;
	    }

2646
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2647
2648
2649
	    /* Note: the selected file can be outside the operating
	     * directory if it is .. or if it is a symlink to 
	     * directory outside the operating directory. */
2650
	    if (check_operating_dir(filelist[selected], 0) != 0) {
2651
2652
2653
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		beep();
		break;
2654
2655
2656
	    }
#endif

2657
2658
2659
2660
2661
	    if (stat(filelist[selected], &st) == -1) {
		statusbar(_("Can't open \"%s\": %s"), filelist[selected], strerror(errno));
		beep();
		break;
	    }
2662

2663
2664
2665
2666
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2667
2668
	    }

2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
	    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
2679
		}
2680
2681
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2682

2683
2684
2685
2686
2687
	    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
2688
	    }
2689
2690
2691
2692
2693
2694
2695

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

Rocco Corsi's avatar
   
Rocco Corsi committed
2696
2697
	/* Goto a specific directory */
	case NANO_GOTO_KEY:
2698
	case NANO_GOTO_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2699
2700
	case 'G': /* Pico compatibility */
	case 'g':
Rocco Corsi's avatar
   
Rocco Corsi committed
2701
	    curs_set(1);
Chris Allegretta's avatar
Chris Allegretta committed
2702
2703
2704
2705
2706
	    j = statusq(0, gotodir_list, "",
#ifndef NANO_SMALL
		0,
#endif
		_("Goto Directory"));
2707
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2708
2709
2710
2711
2712
2713
2714
	    curs_set(0);

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

2715
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2716

2717
2718
2719
	    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
2720
2721
	    }

2722
#ifndef DISABLE_OPERATINGDIR
2723
	    if (check_operating_dir(new_path, 0) != 0) {
2724
2725
2726
2727
2728
2729
2730
		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
2731
2732
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2733
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2734
		break;
2735
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2736
2737

	    /* Start over again with the new path value */
2738
2739
	    free_charptrarray(filelist, numents);
	    free(foo);
2740
2741
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2742
2743
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2744
	/* Stuff we want to abort the browser */
2745
	case NANO_CANCEL_KEY:
2746
	case NANO_EXIT_KEY:
2747
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2748
2749
	case 'E': /* Pico compatibility */
	case 'e':
2750
2751
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2752
2753
2754
2755
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2756
2757
	blank_edit();

2758
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
	    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 */
2773
2774
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2775
2776
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2777
2778
2779
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2780
2781
2782
2783
2784
2785
2786
2787
		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, "--");
2788
2789
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2790
			(int) st.st_size);
2791
2792
2793
2794
2795
2796
		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);
2797
		else /* It's more than 1 k and less than a meg */
2798
2799
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2800
2801
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2802
	    /* Highlight the currently selected file/dir */
2803
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2804
		wattron(edit, A_REVERSE);
2805
2806
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2807
2808
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2809
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2810
2811
2812
2813
2814
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2815
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2816
2817
2818
2819
2820
2821
2822
2823
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
 	wrefresh(edit);
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2824
    } while ((kbinput = get_kbinput(edit, &meta)) != NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
Chris Allegretta's avatar
Chris Allegretta committed
2825
2826
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2827
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2828
2829
    edit_refresh();

Chris Allegretta's avatar
Chris Allegretta committed
2830
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2831
2832
2833
2834
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2835

2836
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2837
 starts do_browser from there, else from the current dir */
2838
char *do_browse_from(const char *inpath)
2839
2840
{
    struct stat st;
2841
    char *bob;
2842
2843
2844
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2845

2846
2847
2848
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2849

2850
2851
2852
2853
2854
2855
2856
2857
    /*
     * 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);
2858
2859
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2860
	    path = getcwd(NULL, PATH_MAX + 1);
2861
	}
2862
    }
2863

2864
2865
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2866
    if (check_operating_dir(path, 0) != 0)
2867
2868
2869
2870
2871
2872
2873
2874
2875
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2876
    return bob;
2877
}
Chris Allegretta's avatar
Chris Allegretta committed
2878
#endif /* !DISABLE_BROWSER */
2879
2880
2881
2882
2883
2884

#ifndef NANO_SMALL
#ifdef ENABLE_NANORC
void load_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2885
    const struct passwd *userage = NULL;
2886
2887
    static char *nanohist;
    char *buf, *ptr;
Chris Allegretta's avatar
Chris Allegretta committed
2888
    char *homenv = getenv("HOME");
2889
2890
2891
    historyheadtype *history = &search_history;


Chris Allegretta's avatar
Chris Allegretta committed
2892
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2893
        nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
2894
2895
2896
2897
        sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2898
        nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
2899
        sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2900
2901
2902
2903
2904
    }

    /* assume do_rcfile has reported missing home dir */

    if (homenv != NULL || userage != NULL) {
2905
	hist = fopen(nanohist, "r");
2906
	if (hist == NULL) {
2907
2908
2909
            if (errno != ENOENT) {
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2910
		rcfile_error(_("Unable to open ~/.nano_history file, %s"), strerror(errno));
2911
	    }
2912
2913
2914
2915
2916
	    free(nanohist);
	} else {
	    buf = charalloc(1024);
	    while (fgets(buf, 1023, hist) != 0) {
		ptr = buf;
2917
		while (*ptr != '\n' && *ptr != '\0' && ptr < buf + 1023)
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
		    ptr++;
		*ptr = '\0';
		if (strlen(buf))
		    update_history(history, buf);
		else
		    history = &replace_history;
	    }
	    fclose(hist);
	    free(buf);
	    free(nanohist);
	    UNSET(HISTORY_CHANGED);
	}
    }
}

/* save histories to ~/.nano_history */
void save_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2937
    const struct passwd *userage = NULL;
2938
    char *nanohist = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2939
    char *homenv = getenv("HOME");
2940
2941
2942
2943
2944
2945
2946
    historytype *h;

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

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

    if (homenv != NULL || userage != NULL) {
2958
	hist = fopen(nanohist, "wb");
2959
	if (hist == NULL) {
2960
2961
2962
2963
2964
2965
	    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
	} else {
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
	    /* write oldest first */
	    for (h = search_history.tail ; h->prev ; h = h->prev) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2966
		h->data = charealloc(h->data, strlen(h->data) + 2);
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
		    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
		    goto come_from;
		}
	    }
	    if (fputs("\n", hist) == EOF) {
		    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
		    goto come_from;
	    }
	    for (h = replace_history.tail ; h->prev ; h = h->prev) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2978
		h->data = charealloc(h->data, strlen(h->data) + 2);
2979
2980
2981
2982
2983
2984
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
		    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
		    goto come_from;
		}
	    }
2985
  come_from:
2986
2987
2988
2989
2990
2991
	    fclose(hist);
	}
	free(nanohist);
    }
}
#endif /* ENABLE_NANORC */
Chris Allegretta's avatar
Chris Allegretta committed
2992
#endif /* !NANO_SMALL */