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

22
23
#include "config.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

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

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

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

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

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

216
217
218
219
	    /* 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
220

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

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

248
249
250
251
252
    /* 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
253
254

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

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

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

286
287
288
289
290
291
292
293
294
    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

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

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

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

329
330
331
332
333
334
#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

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

    return 1;
}

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

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

    return 1;
}

394
/* This function will return the name of the first available extension
Chris Allegretta's avatar
Chris Allegretta committed
395
396
397
 * 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
398
char *get_next_filename(const char *name)
399
400
401
402
403
404
405
406
{
    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
407
    while (1) {
408
409

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

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

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

    return buf;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

572
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
573

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

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

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

588
#ifdef ENABLE_MULTIBUFFER
589
590
591
592
593
594
595
596
597
598
	/* If we've loaded another file, update the titlebar's contents */
	if (loading_file) {
	    clearok(topwin, FALSE);
	    titlebar(NULL);

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

Chris Allegretta's avatar
Chris Allegretta committed
599
600
601
602
603
604
605
606
607
608
609
#ifdef ENABLE_MULTIBUFFER
	if (!loading_file) {
#endif

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

#ifdef ENABLE_MULTIBUFFER
	}
#endif

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

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

Chris Allegretta's avatar
Chris Allegretta committed
621
622
623
624
#ifdef ENABLE_MULTIBUFFER
  skip_insert:
#endif

625
626
627
628
629
    free(inspath);
    inspath = NULL;

    display_main_list();
    return i;
Chris Allegretta's avatar
Chris Allegretta committed
630
631
}

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

    display_main_list();
    return result;
}

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

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

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

731
    if (fileage == NULL || current == NULL || filename == NULL)
732
733
734
	return 1;

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

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

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

    /* save current filename */
753
    open_files->filename = mallocstrcpy(open_files->filename, filename);
754

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

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

Chris Allegretta's avatar
Chris Allegretta committed
778
779
780
781
782
783
784
785
786
787
788
789
790
791
    /* if we're updating, save current modification status (and marking
       status, if available) */
    if (update) {
#ifndef NANO_SMALL
	open_files->file_flags = (MODIFIED & ISSET(MODIFIED)) | (MARK_ISSET & ISSET(MARK_ISSET));
	if (ISSET(MARK_ISSET)) {
	    open_files->file_mark_beginbuf = mark_beginbuf;
	    open_files->file_mark_beginx = mark_beginx;
	}
#else
	open_files->file_flags = (MODIFIED & ISSET(MODIFIED));
#endif
    }

792
793
794
    /* 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
795
796
797
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
798
    }
799
800

#ifdef DEBUG
801
    fprintf(stderr, "filename is %s\n", open_files->filename);
802
803
804
805
806
807
808
809
810
811
812
813
#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)
{
814
    if (open_files == NULL)
815
816
817
818
	return 1;

    /* set up the filename, the file buffer, the total number of lines in
       the file, and the total file size */
819
    filename = mallocstrcpy(filename, open_files->filename);
820
821
822
#ifndef NANO_SMALL
    originalfilestat = open_files->originalfilestat;
#endif
823
    fileage = open_files->fileage;
824
    current = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
825
    filebot = open_files->filebot;
826
827
828
    totlines = open_files->file_totlines;
    totsize = open_files->file_totsize;

Chris Allegretta's avatar
Chris Allegretta committed
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
    /* 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
844

Chris Allegretta's avatar
Chris Allegretta committed
845
846
847
848
#ifdef ENABLE_COLOR
    update_color();
#endif

849
850
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
851
    do_gotopos(open_files->file_lineno, open_files->file_current_x, open_files->file_current_y, open_files->file_placewewant);
852

Chris Allegretta's avatar
Chris Allegretta committed
853
    /* update the titlebar */
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
    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)
{
869
    if (open_files == NULL)
870
871
872
	return 1;

    /* if we're not about to close the current entry, update it before
873
       doing anything */
874
    if (!closing_file)
875
	add_open_file(1);
876

877
    if (open_files->prev == NULL && open_files->next == NULL) {
878
879
880

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

885
    if (open_files->prev != NULL) {
886
887
888
	open_files = open_files->prev;

#ifdef DEBUG
889
	fprintf(stderr, "filename is %s\n", open_files->filename);
890
891
892
893
#endif

    }

894
    else if (open_files->next != NULL) {
895
896

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

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

    }

    load_open_file();

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

911
912
913
914
915
916
917
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

918
919
920
/* This function is used by the shortcut list. */
int open_prevfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
921
    return open_prevfile(0);
922
923
}

924
925
926
927
928
929
930
931
/*
 * 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)
{
932
    if (open_files == NULL)
933
934
935
	return 1;

    /* if we're not about to close the current entry, update it before
936
       doing anything */
937
    if (!closing_file)
938
	add_open_file(1);
939

940
    if (open_files->prev == NULL && open_files->next == NULL) {
941
942
943

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
944
	    statusbar(_("No more open file buffers"));
945
946
947
	return 1;
    }

948
    if (open_files->next != NULL) {
949
950
951
	open_files = open_files->next;

#ifdef DEBUG
952
	fprintf(stderr, "filename is %s\n", open_files->filename);
953
954
955
#endif

    }
956
    else if (open_files->prev != NULL) {
957
958

	/* if we're at the end, wrap around to the beginning */
959
	while (open_files->prev != NULL) {
960
961
962
	    open_files = open_files->prev;

#ifdef DEBUG
963
	    fprintf(stderr, "filename is %s\n", open_files->filename);
964
965
966
967
968
969
970
#endif

	}
    }

    load_open_file();

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

974
975
976
977
978
979
980
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

981
982
983
/* This function is used by the shortcut list. */
int open_nextfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
984
    return open_nextfile(0);
985
986
}

987
988
/*
 * Delete an entry from the open_files filestruct.  After deletion of an
989
 * entry, the next or previous entry is opened, whichever is found first.
990
991
992
993
 * Return 0 on success or 1 on error.
 */
int close_open_file(void)
{
994
    openfilestruct *tmp;
995

996
    if (open_files == NULL)
997
998
	return 1;

Chris Allegretta's avatar
Chris Allegretta committed
999
1000
1001
    /* 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 */
1002
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
1003
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1004

1005
    tmp = open_files;
1006
1007
    if (open_nextfile(1)) {
	if (open_prevfile(1))
1008
1009
1010
	    return 1;
    }

1011
1012
    unlink_opennode(tmp);
    delete_opennode(tmp);
1013
1014
1015
1016
1017

    shortcut_init(0);
    display_main_list();
    return 0;
}
1018
#endif /* MULTIBUFFER */
1019

1020
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
1021
/*
1022
1023
1024
1025
1026
1027
 * 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).
1028
 */
1029
char *get_full_path(const char *origpath)
1030
{
1031
1032
1033
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
1034
    char *expanded_origpath;
1035

1036
    /* first, get the current directory, and tack a slash onto the end of
1037
       it, unless it turns out to be "/", in which case leave it alone */
1038
1039
1040
1041
1042
1043
1044

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

1045
    if (d_here != NULL) {
1046
1047

	align(&d_here);
1048
	if (strcmp(d_here, "/")) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1049
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1050
1051
	    strcat(d_here, "/");
	}
1052
1053
1054
1055
1056

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

1059
	expanded_origpath = real_dir_from_tilde(origpath);
1060
	/* save the value of origpath in both d_there and d_there_file */
1061
1062
1063
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1064

1065
1066
1067
1068
1069
1070
	/* 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
1071
		d_there = charealloc(d_there, strlen(d_there) + 2);
1072
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1073
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1074
1075
1076
1077
		strcat(d_there_file, "/");
	    }
	}

1078
1079
1080
1081
1082
	/* 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 */
1083
	if (last_slash == NULL)
1084
	    d_there = mallocstrcpy(d_there, d_here);
1085
	else {
1086
1087
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1088
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1089
	    null_at(&d_there, last_slash_index + 1);
1090
1091
1092

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

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1103
1104
1105
		/* 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 */
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115

		free(d_there);

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

		align(&d_there);
1116
		if (d_there != NULL) {
1117
1118
1119
1120

		    /* 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
1121
			d_there = charealloc(d_there, strlen(d_there) + 2);
1122
1123
			strcat(d_there, "/");
		    }
1124
1125
1126
		}
		else
		    return NULL;
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
	    }

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

1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
	/* 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);
	}
1148
1149
1150
1151
1152
1153
1154
1155
1156

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

    return newpath;
}
1157
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1158
1159
1160

#ifndef DISABLE_SPELLER
/*
1161
1162
1163
 * 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.
1164
 */
1165
char *check_writable_directory(const char *path)
1166
{
1167
    char *full_path = get_full_path(path);
1168
    int writable;
1169
1170
    struct stat fileinfo;

1171
    /* if get_full_path() failed, return NULL */
1172
    if (full_path == NULL)
1173
	return NULL;
1174
1175
1176

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

    /* if the full path doesn't end in a slash (meaning get_full_path()
1180
1181
1182
1183
       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);
1184
	return NULL;
1185
    }
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195

    /* 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
1196
1197
1198
1199
1200
1201
1202
 * 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.
1203
 */
1204
1205
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1206
1207
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1208
    int filedesc;
1209

1210
1211
      /* 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,
1212
         leave full_tempdir set to NULL */
1213
    TMPDIR_env = getenv("TMPDIR");
1214
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1215
	full_tempdir = check_writable_directory(TMPDIR_env);
1216

1217
1218
1219
    /* 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 */
1220
    if (full_tempdir == NULL && dirname != NULL)
1221
	full_tempdir = check_writable_directory(dirname);
1222
1223
1224

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1225
    if (full_tempdir == NULL)
1226
	full_tempdir = check_writable_directory(P_tmpdir);
1227
1228

    /* if P_tmpdir didn't work, use /tmp instead */
1229
    if (full_tempdir == NULL) {
1230
1231
1232
1233
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1234
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1235
1236

    /* like tempnam(), use only the first 5 characters of the prefix */
1237
1238
1239
1240
1241
1242
1243
1244
    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) {
1245
	close(filedesc);
1246
1247
	unlink(full_tempdir);
	return full_tempdir;
1248
    }
1249
1250
1251

    free(full_tempdir);
    return NULL;
1252
1253
}
#endif /* !DISABLE_SPELLER */
1254
1255

#ifndef DISABLE_OPERATINGDIR
1256
1257
1258
1259
1260
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1261
    if (operating_dir == NULL)
1262
1263
1264
1265
1266
	return;
    full_operating_dir = get_full_path(operating_dir);

    /* If get_full_path() failed or the operating directory is
       inaccessible, unset operating_dir. */
1267
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1268
1269
1270
1271
1272
1273
1274
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

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

1288
1289
1290
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1291
1292

    /* if no operating directory is set, don't bother doing anything */
1293
    if (operating_dir == NULL)
1294
1295
1296
	return 0;

    fullpath = get_full_path(currpath);
1297
    if (fullpath == NULL)
1298
1299
1300
1301
1302
1303
1304
	return 1;

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

    /* if both searches failed, we're outside the operating directory */
1305
    /* otherwise */
1306
1307
1308
1309
1310
    /* check the search results; if the full operating directory path is
       not at the beginning of the full current path (for normal usage)
       and vice versa (for tab completion, if we're allowing it), we're
       outside the operating directory */
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1311
1312
	retval = 1;
    free(fullpath);	
1313
    /* otherwise, we're still inside it */
1314
    return retval;
1315
}
1316
1317
#endif

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

Chris Allegretta's avatar
Chris Allegretta committed
1347
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1348
1349
1350
	statusbar(_("Cancelled"));
	return -1;
    }
1351
1352
    if (!tmp)
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1353
    fileptr = fileage;
1354

1355
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1356

1357
#ifndef DISABLE_OPERATINGDIR
1358
1359
    /* If we're writing a temporary file, we're probably going outside
       the operating directory, so skip the operating directory test. */
1360
    if (!tmp && operating_dir != NULL && check_operating_dir(realname, 0)) {
1361
1362
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1363
1364
1365
    }
#endif

1366
1367
    /* Save the state of file at the end of the symlink (if there is
       one). */
1368
1369
    realexists = stat(realname, &st);

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

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

	/* open the original file to copy to the backup */
	f = fopen(realname, "rb");
1391
	if (f == NULL) {
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
	    statusbar(_("Could not read %s for backup: %s"), realname,
		strerror(errno));
	    return -1;
	}

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

	/* get a file descriptor for the destination backup file */
	backup_file = fopen(backupname, "wb");
1402
	if (backup_file == NULL) {
1403
1404
1405
1406
1407
1408
	    statusbar(_("Couldn't write backup: %s"), strerror(errno));
	    free(backupname);
	    return -1;
	}

#ifdef DEBUG
1409
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
#endif

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

	if (chmod(backupname, originalfilestat.st_mode) == -1)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1421
	    statusbar(_("Could not set permissions %o on backup %s: %s"),
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
			originalfilestat.st_mode, backupname,
			strerror(errno));

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

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

	free(backupname);
    }
#endif

1439
1440
    /* Stat the link itself for the check... */
    anyexists = lstat(realname, &lst);
1441

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

	/* First, just give up if we couldn't even open the file */
1462
	if (fd == -1) {
1463
	    if (!tmp && ISSET(TEMP_OPT)) {
Chris Allegretta's avatar
Chris Allegretta committed
1464
		UNSET(TEMP_OPT);
1465
1466
1467
1468
1469
		retval = do_writeout(filename, 1, 0);
	    } else
		statusbar(_("Could not open file for writing: %s"),
			strerror(errno));
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1470
	}
1471

Chris Allegretta's avatar
Chris Allegretta committed
1472
1473
1474
    }
    /* Don't follow symlink.  Create new file. */
    else {
1475
	buf = charalloc(strlen(realname) + 8);
1476
	strcpy(buf, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1477
1478
	strcat(buf, ".XXXXXX");
	if ((fd = mkstemp(buf)) == -1) {
Chris Allegretta's avatar
Chris Allegretta committed
1479
1480
	    if (ISSET(TEMP_OPT)) {
		UNSET(TEMP_OPT);
1481
1482
1483
1484
1485
		retval = do_writeout(filename, 1, 0);
	    } else
		statusbar(_("Could not open file for writing: %s"),
			strerror(errno));
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1486
1487
1488
	}
    }

Chris Allegretta's avatar
Chris Allegretta committed
1489
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
1490
    dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
1491
#endif
1492

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1493
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1494
    if (f == NULL) {
1495
1496
	statusbar(_("Could not open file for writing: %s"), strerror(errno));
	goto cleanup_and_exit;
1497
1498
    }

Chris Allegretta's avatar
Chris Allegretta committed
1499
    while (fileptr != NULL && fileptr->next != NULL) {
1500
	int data_len;
1501

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

1506
	data_len = strlen(fileptr->data);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1507
1508
1509
1510

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

1511
	size = fwrite(fileptr->data, 1, data_len, f);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1512
1513
1514
1515

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

1516
	if (size < data_len) {
Chris Allegretta's avatar
Chris Allegretta committed
1517
1518
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
1519
	    fclose(f);
1520
	    goto cleanup_and_exit;
1521
	}
Chris Allegretta's avatar
Chris Allegretta committed
1522
#ifdef DEBUG
1523
	else
1524
	    fprintf(stderr, "Wrote >%s\n", fileptr->data);
Chris Allegretta's avatar
Chris Allegretta committed
1525
#endif
1526
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1527
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1528
	    putc('\r', f);
Chris Allegretta's avatar
Chris Allegretta committed
1529
1530

	if (!ISSET(MAC_FILE))
1531
#endif
1532
	    putc('\n', f);
Chris Allegretta's avatar
Chris Allegretta committed
1533
1534
1535
1536
1537
1538

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

    if (fileptr != NULL) {
1539
	int data_len = strlen(fileptr->data);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1540
1541
1542
1543

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

1544
	size = fwrite(fileptr->data, 1, data_len, f);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1545
1546
1547
1548

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

1549
	if (size < data_len) {
Chris Allegretta's avatar
Chris Allegretta committed
1550
1551
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
1552
	    goto cleanup_and_exit;
1553
	} else if (data_len > 0) {
1554
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1555
	    if (ISSET(DOS_FILE) || ISSET(MAC_FILE)) {
1556
		if (putc('\r', f) == EOF) {
1557
1558
		    statusbar(_("Could not open file for writing: %s"),
			  strerror(errno));
1559
		    fclose(f);
1560
		    goto cleanup_and_exit;
1561
		}
1562
		lineswritten++;
1563
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1564
1565

	    if (!ISSET(MAC_FILE))
1566
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1567
	    {
1568
		if (putc('\n', f) == EOF) {
Chris Allegretta's avatar
Chris Allegretta committed
1569
		    statusbar(_("Could not open file for writing: %s"),
Chris Allegretta's avatar
Chris Allegretta committed
1570
			  strerror(errno));
1571
		    fclose(f);
1572
		    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1573
		}
1574
		lineswritten++;
Chris Allegretta's avatar
Chris Allegretta committed
1575
1576
1577
1578
	    }
	}
    }

1579
    if (fclose(f) != 0) {
1580
	statusbar(_("Could not close %s: %s"), realname, strerror(errno));
Chris Allegretta's avatar
Chris Allegretta committed
1581
	unlink(buf);
1582
	goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1583
1584
    }

1585
1586
    /* if we're prepending, open the real file, and append it here */
    if (append == 2) {
1587
1588
1589
	int fd_source, fd_dest;
	FILE *f_source, *f_dest;
	int prechar;
1590

1591
	if ((fd_dest = open(buf, O_WRONLY | O_APPEND, (S_IRUSR | S_IWUSR))) == -1) {
1592
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
1593
	    goto cleanup_and_exit;
1594
	}
1595
	f_dest = fdopen(fd_dest, "wb");
1596
	if (f_dest == NULL) {
1597
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
1598
	    close(fd_dest);
1599
	    goto cleanup_and_exit;
1600
	}
1601
	if ((fd_source = open(realname, O_RDONLY | O_CREAT)) == -1) {
1602
	    statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
1603
	    fclose(f_dest);
1604
	    goto cleanup_and_exit;
1605
1606
	}
	f_source = fdopen(fd_source, "rb");
1607
	if (f_source == NULL) {
1608
1609
1610
	    statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
	    fclose(f_dest);
	    close(fd_source);
1611
	    goto cleanup_and_exit;
1612
1613
	}

1614
1615
1616
1617
1618
1619
        /* Doing this in blocks is an exercise left to some other reader. */
	while ((prechar = getc(f_source)) != EOF) {
	    if (putc(prechar, f_dest) == EOF) {
		statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
		fclose(f_source);
		fclose(f_dest);
1620
		goto cleanup_and_exit;
1621
1622
	    }
	}
1623

1624
1625
1626
1627
	if (ferror(f_source)) {
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
	    fclose(f_source);
	    fclose(f_dest);
1628
	    goto cleanup_and_exit;
1629
1630
1631
1632
	}
	    
	fclose(f_source);
	fclose(f_dest);
1633
1634
    }

1635
    if (realexists == -1 || tmp ||
1636
	(ISSET(NOFOLLOW_SYMLINKS) && S_ISLNK(lst.st_mode))) {
Chris Allegretta's avatar
Chris Allegretta committed
1637

1638
	/* Use default umask as file permissions if file is a new file. */
1639
1640
	mask = umask(0);
	umask(mask);
Chris Allegretta's avatar
Chris Allegretta committed
1641

1642
	if (tmp)	/* We don't want anyone reading our temporary file! */
Chris Allegretta's avatar
Chris Allegretta committed
1643
	    mask = S_IRUSR | S_IWUSR;
1644
	else
Chris Allegretta's avatar
Chris Allegretta committed
1645
1646
	    mask = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH |
		S_IWOTH) & ~mask;
1647
    } else
Chris Allegretta's avatar
Chris Allegretta committed
1648
	/* Use permissions from file we are overwriting. */
1649
1650
	mask = st.st_mode;

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

1679
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1680
	if (!nonamechange) {
1681
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1682
1683
1684
1685
1686
#ifdef ENABLE_COLOR
	    update_color();
	    edit_refresh();
#endif
	}
1687

1688
1689
1690
1691
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1692
	statusbar(P_("Wrote %d line", "Wrote %d lines", lineswritten),
1693
			lineswritten);
1694
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1695
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1696
    }
1697
1698
1699
1700
1701
1702
1703

    retval = 1;

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

1706
int do_writeout(const char *path, int exiting, int append)
Chris Allegretta's avatar
Chris Allegretta committed
1707
1708
{
    int i = 0;
1709
1710
1711
#ifdef NANO_EXTRA
    static int did_cred = 0;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1712

1713
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1714
    currshortcut = writefile_list;
1715
1716
#endif

1717
    answer = mallocstrcpy(answer, path);
Chris Allegretta's avatar
Chris Allegretta committed
1718

1719
    if (exiting && ISSET(TEMP_OPT)) {
1720
	if (filename[0] != '\0') {
1721
	    i = write_file(answer, 0, 0, 0);
1722
1723
	    display_main_list();
	    return i;
1724
	} else {
Chris Allegretta's avatar
Chris Allegretta committed
1725
1726
1727
1728
1729
	    UNSET(TEMP_OPT);
	    do_exit();

	    /* They cancelled, abort quit */
	    return -1;
1730
	}
Chris Allegretta's avatar
Chris Allegretta committed
1731
1732
1733
    }

    while (1) {
1734
#ifndef NANO_SMALL
1735
1736
	const char *formatstr, *backupstr;

1737
1738
1739
1740
1741
1742
1743
	if (ISSET(MAC_FILE))
	   formatstr = _(" [Mac Format]");
	else if (ISSET(DOS_FILE))
	   formatstr = _(" [DOS Format]");
	else
	   formatstr = "";

1744
1745
1746
1747
1748
	if (ISSET(BACKUP_FILE))
	   backupstr = _(" [Backup]");
	else
	   backupstr = "";

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

1783
1784
1785
1786
1787
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
	    return 0;
	}
Chris Allegretta's avatar
Chris Allegretta committed
1788

1789
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1790
	if (i == NANO_TOFILES_KEY) {
1791
	    char *tmp = do_browse_from(answer);
1792

1793
	    currshortcut = writefile_list;
1794
1795
1796
1797
	    if (tmp == NULL)
		continue;
	    free(answer);
	    answer = tmp;
1798
	} else
1799
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1800
#ifndef NANO_SMALL
1801
1802
1803
	if (i == TOGGLE_DOS_KEY) {
	    UNSET(MAC_FILE);
	    TOGGLE(DOS_FILE);
1804
	    continue;
1805
1806
1807
	} else if (i == TOGGLE_MAC_KEY) {
	    UNSET(DOS_FILE);
	    TOGGLE(MAC_FILE);
1808
	    continue;
1809
1810
	} else if (i == TOGGLE_BACKUP_KEY) {
	    TOGGLE(BACKUP_FILE);
1811
1812
	    continue;
	} else
1813
#endif /* !NANO_SMALL */
1814
1815
1816
1817
1818
1819
1820
	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
1821

Chris Allegretta's avatar
Chris Allegretta committed
1822
#ifdef DEBUG
1823
	fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1824
#endif
1825
1826

#ifdef NANO_EXTRA
1827
	if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy")
1828
		&& !did_cred) {
1829
1830
1831
1832
	    do_credits();
	    did_cred = 1;
	    return -1;
	}
1833
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1834
	if (append == 0 && strcmp(answer, filename)) {
1835
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1836

1837
	    if (!stat(answer, &st)) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1838
		i = do_yesno(0, _("File exists, OVERWRITE ?"));
1839
1840
1841
1842
1843
1844
		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
1845
		) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1846
		i = do_yesno(0, _("Save file under DIFFERENT NAME ?"));
1847
1848
1849
		if (i == 0 || i == -1)
		    continue;
	    }
1850
	}
Chris Allegretta's avatar
Chris Allegretta committed
1851

1852
#ifndef NANO_SMALL
1853
1854
1855
	/* Here's where we allow the selected text to be written to 
	   a separate file. */
	if (ISSET(MARK_ISSET) && !exiting) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1856
	    filestruct *fileagebak = fileage;
1857
1858
	    filestruct *filebotbak = filebot;
	    filestruct *cutback = cutbuffer;
1859
1860
	    int oldmod = ISSET(MODIFIED);
		/* write_file() unsets the MODIFIED flag. */
1861

1862
	    cutbuffer = NULL;
1863

1864
1865
1866
	    /* Put the marked text in the cutbuffer without changing
	       the open file. */
	    cut_marked_segment(current, current_x, mark_beginbuf,
1867
1868
1869
				mark_beginx, 0);

	    fileage = cutbuffer;
1870
	    filebot = get_cutbottom();
1871
	    i = write_file(answer, 0, append, 1);
1872
1873

	    /* Now restore everything */
1874
	    free_filestruct(cutbuffer);
1875
1876
1877
1878
1879
1880
	    fileage = fileagebak;
	    filebot = filebotbak;
	    cutbuffer = cutback;
	    if (oldmod)
		set_modified();
	} else
1881
#endif /* !NANO_SMALL */
1882
	    i = write_file(answer, 0, append, 0);
1883

1884
#ifdef ENABLE_MULTIBUFFER
1885
1886
1887
1888
	/* If we're not about to exit, update the current entry in
	   the open_files structure. */
	if (!exiting)
	    add_open_file(1);
1889
#endif
1890
1891
1892
	display_main_list();
	return i;
    } /* while (1) */
Chris Allegretta's avatar
Chris Allegretta committed
1893
1894
1895
1896
}

int do_writeout_void(void)
{
1897
    return do_writeout(filename, 0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
1898
}
Chris Allegretta's avatar
Chris Allegretta committed
1899

1900
/* Return a malloc()ed string containing the actual directory, used
1901
1902
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1903
{
Chris Allegretta's avatar
Chris Allegretta committed
1904
    char *dirtmp = NULL;
1905

1906
    if (buf[0] == '~') {
1907
1908
1909
1910
1911
1912
1913
	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++)
	    ;

1914
1915
	/* Determine home directory using getpwuid() or getpwent(),
	   don't rely on $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
1916
1917
1918
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
1919
1920
1921
1922
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
			strncmp(userdata->pw_name, buf + 1, i - 1));
Chris Allegretta's avatar
Chris Allegretta committed
1923
1924
	}
	endpwent();
1925

1926
	if (userdata != NULL) {	/* User found */
1927
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1928
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1929
	}
1930
    }
1931

1932
1933
1934
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1935
    return dirtmp;
1936
1937
}

Chris Allegretta's avatar
Chris Allegretta committed
1938
#ifndef DISABLE_TABCOMP
1939
1940
1941
/* 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
1942
int append_slash_if_dir(char *buf, int *lastwastab, int *place)
1943
{
1944
    char *dirptr = real_dir_from_tilde(buf);
1945
    struct stat fileinfo;
1946
    int ret = 0;
1947

1948
    assert(dirptr != buf);
1949

1950
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
1951
	strncat(buf, "/", 1);
1952
	(*place)++;
1953
	/* now we start over again with # of tabs so far */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1954
	*lastwastab = 0;
1955
	ret = 1;
1956
1957
    }

1958
    free(dirptr);
1959
    return ret;
1960
}
Chris Allegretta's avatar
Chris Allegretta committed
1961
1962

/*
Chris Allegretta's avatar
Chris Allegretta committed
1963
1964
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1965
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
 *
 * 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)
{
1983
    char **matches = (char **)NULL;
1984
    char *matchline = NULL;
1985
    struct passwd *userdata;
1986

1987
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1988
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
1989

1990
    strcat(buf, "*");
1991

1992
    while ((userdata = getpwent()) != NULL) {
1993

1994
	if (check_wildcard_match(userdata->pw_name, &buf[1]) == TRUE) {
1995
1996
1997
1998

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

2000
2001
2002
2003
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2004
2005
	    if (operating_dir != NULL) {
		if (check_operating_dir(userdata->pw_dir, 1) != 0)
2006
2007
2008
2009
		    continue;
	    }
#endif

2010
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2011
2012
2013
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
	    ++*num_matches;
2014

2015
2016
2017
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2018
	}
2019
2020
    }
    endpwent();
2021

2022
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2023
2024
2025
2026
2027
2028
2029
}

/* 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
2030
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2031
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2032
2033
2034
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2035
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2036
2037
2038
2039

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

2040
    /* Okie, if there's a / in the buffer, strip out the directory part */
2041
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2042
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2043
2044
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2045
	    tmp--;
2046

Chris Allegretta's avatar
Chris Allegretta committed
2047
2048
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2049
2050
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2051

Chris Allegretta's avatar
Chris Allegretta committed
2052
    } else {
2053
2054

#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2055
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
2056
#else
2057
	/* The better, but apparently segfault-causing way */
Chris Allegretta's avatar
Chris Allegretta committed
2058
	if ((dirname = getcwd(NULL, 0)) == NULL)
2059
#endif /* PATH_MAX */
Chris Allegretta's avatar
Chris Allegretta committed
2060
2061
2062
2063
2064
2065
	    return matches;
	else
	    tmp = buf;
    }

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

Chris Allegretta's avatar
Chris Allegretta committed
2071
2072
2073
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2074
2075

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2076
    fprintf(stderr, "\nDir = %s\n", dirname);
2077
2078
2079
2080
2081
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2082
    dir = opendir(dirname);
2083
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2084
2085
2086
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2087
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2088
2089
2090
2091
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2092
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2093
2094
2095
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
2096
2097
2098
2099

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2100
2101
2102
2103
2104
2105
2106
2107

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

2108
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2109
2110
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
		if (check_operating_dir(tmp2, 1)) {
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2121
	    tmp2 = NULL;
2122
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2123
2124
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2125
	    ++*num_matches;
2126
2127
2128
2129

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2130
2131
	}
    }
2132
2133
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2134

2135
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2136
2137
}

Chris Allegretta's avatar
Chris Allegretta committed
2138
2139
/* 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
2140
char *input_tab(char *buf, int place, int *lastwastab, int *newplace, int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2141
2142
{
    /* Do TAB completion */
2143
    static int num_matches = 0, match_matches = 0;
2144
    static char **matches = (char **)NULL;
2145
    int pos = place, i = 0, col = 0, editline = 0;
2146
    int longestname = 0, is_dir = 0;
2147
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2148

2149
2150
    *list = 0;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2151
    if (*lastwastab == FALSE) {
2152
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2153

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2154
	*lastwastab = 1;
2155

Chris Allegretta's avatar
Chris Allegretta committed
2156
2157
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2158
2159
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2160

2161
2162
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2163
2164

	/* skip any leading white space */
2165
	while (*tmp && isspace((int)*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2166
2167
2168
2169
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2170
2171
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2172
	    free(matches);
2173
	    matches = (char **)NULL;
2174
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2175
2176
2177
2178
2179
	}

	/* 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
2180
2181
2182
2183
2184
	/* 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. */
2185
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2186
	    buf = mallocstrcpy(buf, tmp);
2187
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2188
	}
2189
2190
2191
2192
2193
2194
	/* 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
2195
2196
2197

	/* Try to match everything in the current working directory that
	 * matches.  */
2198
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2199
2200
2201
	    matches = cwd_tab_completion(tmp, &num_matches);

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

2204
2205
2206
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2207
	/* Did we find exactly one match? */
2208
	switch (num_matches) {
2209
	case 0:
2210
	    blank_edit();
2211
	    wrefresh(edit);
2212
2213
	    break;
	case 1:
2214

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

2217
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2218
2219
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2220
		tmp++;
2221
	    } else
2222
2223
		tmp = buf;

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

2227
	    if (is_dir != 0)
2228
		break;
2229
2230

	    copyto = tmp;
2231
2232
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2233
2234
		tmp++;

2235
	    /* write out the matched name */
2236
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2237
2238
	    *newplace += strlen(matches[0]) - pos;

2239
2240
2241
2242
2243
2244
2245
	    /* 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;

2246
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2247
	    append_slash_if_dir(buf, lastwastab, newplace);
2248

2249
2250
	    break;
	default:
2251
	    /* Check to see if all matches share a beginning, and, if so,
2252
	       tack it onto buf and then beep */
2253

2254
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2255
2256
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2257
		tmp++;
2258
	    } else
2259
2260
		tmp = buf;

2261
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2262
		 pos <= strlen(matches[0]); pos++)
2263
2264
		tmp++;

2265
2266
2267
2268
2269
2270
2271
2272
2273
	    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++;
		}
2274
2275
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2276
		    /* All the matches have the same character at pos+1,
2277
		       so paste it into buf... */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2278
		    buf = charealloc(buf, strlen(buf) + 2);
2279
		    strncat(buf, matches[0] + pos, 1);
2280
		    *newplace += 1;
2281
		    pos++;
2282
		} else {
2283
2284
2285
2286
2287
		    beep();
		    break;
		}
	    }
	}
Chris Allegretta's avatar
Chris Allegretta committed
2288
2289
2290
2291
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2292
	if (matches != NULL && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2293
2294
2295
2296
2297

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

2298
	    editline = 0;
2299

2300
2301
2302
2303
2304
2305
2306
2307
	    /* 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;

2308
	    foo = charalloc(longestname + 5);
2309

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

2313
		/* make each filename shown be the same length as the longest
2314
		   filename, with two spaces at the end */
2315
2316
2317
2318
2319
2320
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2321
2322
		/* Disable el cursor */
		curs_set(0);
2323
2324
2325
2326
2327
2328
		/* 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 */
2329
		if (col > COLS - longestname && i + 1 < num_matches) {
2330
2331
		    editline++;
		    wmove(edit, editline, 0);
2332
2333
2334
2335
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2336
2337
2338
		    col = 0;
		}
	    }
2339
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2340
	    wrefresh(edit);
2341
	    *list = 1;
2342
2343
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2344
2345
    }

2346
2347
2348
2349
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
    if (*list == 0)
	edit_refresh();
2350
2351
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2352
}
2353
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2354

2355
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
2356
/* Return the stat of the file pointed to by path */
Chris Allegretta's avatar
Chris Allegretta committed
2357
2358
struct stat filestat(const char *path)
{
Chris Allegretta's avatar
Chris Allegretta committed
2359
2360
    struct stat st;

2361
    stat(path, &st);
Chris Allegretta's avatar
Chris Allegretta committed
2362
2363
2364
2365
    return st;
}

/* Our sort routine for file listings - sort directories before
2366
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2367
2368
int diralphasort(const void *va, const void *vb)
{
2369
2370
2371
2372
    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
2373

2374
    if (aisdir != 0 && bisdir == 0)
2375
	return -1;
2376
    if (aisdir == 0 && bisdir != 0)
2377
	return 1;
2378

2379
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2380
2381
}

2382
/* Free our malloc()ed memory */
Chris Allegretta's avatar
Chris Allegretta committed
2383
2384
void free_charptrarray(char **array, int len)
{
2385
2386
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2387
2388
2389
    free(array);
}

2390
2391
2392
/* 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
2393
{
2394
    const char *tmp = foo + strlen(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2395
2396
2397
2398

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

2399
2400
    if (*tmp == '/')
	tmp++;
Chris Allegretta's avatar
Chris Allegretta committed
2401
2402
2403
2404

    return tmp;
}

2405
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2406
2407
void striponedir(char *foo)
{
2408
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2409

2410
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2411
    /* Don't strip the root dir */
2412
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2413
2414
	return;

2415
2416
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2417
    if (*tmp == '/')
2418
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2419
2420
2421
2422
2423

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

    if (tmp != foo)
2424
2425
2426
2427
2428
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2429
    }
Chris Allegretta's avatar
Chris Allegretta committed
2430
2431
}

2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
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;
}

2442
/* Initialize the browser code, including the list of files in *path */
2443
char **browser_init(const char *path, int *longest, int *numents)
2444
2445
2446
{
    DIR *dir;
    struct dirent *next;
2447
    char **filelist;
2448
    int i = 0;
2449
    size_t path_len;
2450
2451

    dir = opendir(path);
2452
    if (dir == NULL)
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
	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
2466
    filelist = (char **)nmalloc(*numents * sizeof (char *));
2467

2468
2469
2470
2471
    if (!strcmp(path, "/"))
	path = "";
    path_len = strlen(path);

2472
2473
2474
2475
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;

2476
2477
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2478
2479
	i++;
    }
2480
    closedir(dir);
2481
2482
2483
2484
2485
2486
2487

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2488
/* Our browser function.  inpath is the path to start browsing from */
2489
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2490
2491
2492
2493
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2494
2495
2496
    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;
2497
    char **filelist = (char **)NULL;
2498
#ifndef DISABLE_MOUSE
2499
2500
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2501

2502
2503
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2504
2505
2506
    /* 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
2507
2508
2509
2510
2511
    if (path != NULL && strcmp(path, inpath)) {
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2512
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2513
    if (path == NULL)
2514
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2515
2516

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

2519
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2520
2521
2522
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2523
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2524
2525
2526
2527
2528
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2529
2530

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2531
    do {
2532
2533
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2534
2535
2536

	blank_statusbar_refresh();

2537
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2538
	currshortcut = browser_list;
2539
2540
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2541
2542
 	editline = 0;
	col = 0;
2543
	    
2544
	/* Compute line number we're on now, so we don't divide by zero later */
2545
2546
2547
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2548
2549

	switch (kbinput) {
2550

2551
#ifndef DISABLE_MOUSE
2552
	case KEY_MOUSE:
2553
	    if (getmouse(&mevent) == ERR)
2554
		return retval;
2555
2556
2557
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2558
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2559
2560
2561
2562
		int selectedbackup = selected;

		mevent.y -= 2;

2563
2564
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2565
		selected = (lineno / editwinrows) * editwinrows * width
2566
2567
2568
2569
2570
2571
			+ 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--;
2572
2573
2574

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2575
		if (selected > numents - 1)
2576
		    selected = numents - 1;
2577
		else if (selectedbackup == selected)
2578
2579
2580
2581
		    ungetch('s');	/* Unget the 'select' key */
	    } else	/* Must be clicking a shortcut */
		do_mouse();

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

2630
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2631
2632
2633
	    /* 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. */
2634
2635
2636
2637
	    if (check_operating_dir(filelist[selected], FALSE)) {
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		beep();
		break;
2638
2639
2640
	    }
#endif

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

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

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

2667
2668
2669
2670
2671
	    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
2672
	    }
2673
2674
2675
2676
2677
2678
2679

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

Rocco Corsi's avatar
   
Rocco Corsi committed
2680
2681
	/* Goto a specific directory */
	case NANO_GOTO_KEY:
2682
	case NANO_GOTO_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2683
2684
	case 'G': /* Pico compatibility */
	case 'g':
Rocco Corsi's avatar
   
Rocco Corsi committed
2685
	    curs_set(1);
Chris Allegretta's avatar
Chris Allegretta committed
2686
2687
2688
2689
2690
	    j = statusq(0, gotodir_list, "",
#ifndef NANO_SMALL
		0,
#endif
		_("Goto Directory"));
2691
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2692
2693
2694
2695
2696
2697
2698
	    curs_set(0);

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

2699
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2700

2701
2702
2703
	    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
2704
2705
	    }

2706
2707
2708
2709
2710
2711
2712
2713
2714
#ifndef DISABLE_OPERATINGDIR
	    if (check_operating_dir(new_path, FALSE)) {
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		free(new_path);
		break;
	    }
#endif

	    if (!readable_dir(new_path)) {
Rocco Corsi's avatar
   
Rocco Corsi committed
2715
2716
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2717
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2718
		break;
2719
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2720
2721

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

Chris Allegretta's avatar
Chris Allegretta committed
2728
	/* Stuff we want to abort the browser */
2729
	case NANO_CANCEL_KEY:
2730
	case NANO_EXIT_KEY:
2731
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2732
2733
	case 'E': /* Pico compatibility */
	case 'e':
2734
2735
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2736
2737
2738
2739
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2740
2741
	blank_edit();

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

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

Chris Allegretta's avatar
Chris Allegretta committed
2793
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2794
2795
2796
2797
2798
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2799
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2800
2801
2802
2803
2804
2805
2806
2807
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
 	wrefresh(edit);
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2808
    } while ((kbinput = get_kbinput(edit, &meta)) != NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
Chris Allegretta's avatar
Chris Allegretta committed
2809
2810
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2811
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2812
2813
    edit_refresh();

Chris Allegretta's avatar
Chris Allegretta committed
2814
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2815
2816
2817
2818
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2819

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

2830
2831
2832
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2833

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

2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
    if (check_operating_dir(path, FALSE))
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2860
    return bob;
2861
}
Chris Allegretta's avatar
Chris Allegretta committed
2862
#endif /* !DISABLE_BROWSER */
2863
2864
2865
2866
2867
2868

#ifndef NANO_SMALL
#ifdef ENABLE_NANORC
void load_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2869
    const struct passwd *userage = NULL;
2870
2871
    static char *nanohist;
    char *buf, *ptr;
Chris Allegretta's avatar
Chris Allegretta committed
2872
    char *homenv = getenv("HOME");
2873
2874
2875
    historyheadtype *history = &search_history;


Chris Allegretta's avatar
Chris Allegretta committed
2876
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2877
        nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
2878
2879
2880
2881
        sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2882
        nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
2883
        sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2884
2885
2886
2887
2888
    }

    /* assume do_rcfile has reported missing home dir */

    if (homenv != NULL || userage != NULL) {
2889
	hist = fopen(nanohist, "r");
2890
	if (hist == NULL) {
2891
2892
2893
            if (errno != ENOENT) {
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2894
		rcfile_error(_("Unable to open ~/.nano_history file, %s"), strerror(errno));
2895
	    }
2896
2897
2898
2899
2900
	    free(nanohist);
	} else {
	    buf = charalloc(1024);
	    while (fgets(buf, 1023, hist) != 0) {
		ptr = buf;
2901
		while (*ptr != '\n' && *ptr != '\0' && ptr < buf + 1023)
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
		    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
2921
    const struct passwd *userage = NULL;
2922
    char *nanohist = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2923
    char *homenv = getenv("HOME");
2924
2925
2926
2927
2928
2929
2930
    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
2931
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2932
	nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
2933
2934
2935
2936
	sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2937
	nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
2938
	sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2939
2940
2941
    }

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