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

22
23
#include "config.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return 1;
}

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

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

    return 1;
}

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

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

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

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

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

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

    return buf;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

578
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
579

Chris Allegretta's avatar
Chris Allegretta committed
580
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
581
	dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
582
#endif
583

584
#ifdef ENABLE_MULTIBUFFER
585
	if (loading_file)
586
	    load_file(0);
587
588
589
	else
#endif
	    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
590
591

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

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

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

#ifdef ENABLE_MULTIBUFFER
	}
#endif

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

    } else {
	statusbar(_("Cancelled"));
624
	i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
625
    }
626

Chris Allegretta's avatar
Chris Allegretta committed
627
628
629
630
#ifdef ENABLE_MULTIBUFFER
  skip_insert:
#endif

631
632
633
634
635
    free(inspath);
    inspath = NULL;

    display_main_list();
    return i;
Chris Allegretta's avatar
Chris Allegretta committed
636
637
}

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

    display_main_list();
    return result;
}

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

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

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

737
    if (fileage == NULL || current == NULL || filename == NULL)
738
739
740
	return 1;

    /* if no entries, make the first one */
741
    if (open_files == NULL)
742
	open_files = make_new_opennode(NULL);
743
744
745
746
747
748
749

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

753
754
	tmp = make_new_opennode(NULL);
	splice_opennode(open_files, tmp, open_files->next);
755
756
757
758
	open_files = open_files->next;
    }

    /* save current filename */
759
    open_files->filename = mallocstrcpy(open_files->filename, filename);
760

761
762
763
764
765
#ifndef NANO_SMALL
    /* save the file's stat */
    open_files->originalfilestat = originalfilestat;
#endif

766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
    /* 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 */
782
    open_files->file_lineno = current->lineno;
783

784
785
786
787
    /* start with default modification status: unmodified (and marking
       status, if available: unmarked) */
    open_files->file_flags = 0;

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

805
806
807
    /* 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
808
809
810
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
811
    }
812
813

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

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

Chris Allegretta's avatar
Chris Allegretta committed
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
    /* 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
857

Chris Allegretta's avatar
Chris Allegretta committed
858
859
860
861
#ifdef ENABLE_COLOR
    update_color();
#endif

862
863
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
864
    do_gotopos(open_files->file_lineno, open_files->file_current_x, open_files->file_current_y, open_files->file_placewewant);
865

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

    /* if we're not about to close the current entry, update it before
886
       doing anything */
887
    if (!closing_file)
888
	add_open_file(1);
889

890
    if (open_files->prev == NULL && open_files->next == NULL) {
891
892
893

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
894
	    statusbar(_("No more open file buffers"));
895
896
897
	return 1;
    }

898
    if (open_files->prev != NULL) {
899
900
901
	open_files = open_files->prev;

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

    }

907
    else if (open_files->next != NULL) {
908
909

	/* if we're at the beginning, wrap around to the end */
910
	while (open_files->next != NULL)
911
912
913
	    open_files = open_files->next;

#ifdef DEBUG
914
	    fprintf(stderr, "filename is %s\n", open_files->filename);
915
916
917
918
919
920
#endif

    }

    load_open_file();

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

924
925
926
927
928
929
930
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

931
932
933
/* This function is used by the shortcut list. */
int open_prevfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
934
    return open_prevfile(0);
935
936
}

937
938
939
940
941
942
943
944
/*
 * 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)
{
945
    if (open_files == NULL)
946
947
948
	return 1;

    /* if we're not about to close the current entry, update it before
949
       doing anything */
950
    if (!closing_file)
951
	add_open_file(1);
952

953
    if (open_files->prev == NULL && open_files->next == NULL) {
954
955
956

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
957
	    statusbar(_("No more open file buffers"));
958
959
960
	return 1;
    }

961
    if (open_files->next != NULL) {
962
963
964
	open_files = open_files->next;

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

    }
969
    else if (open_files->prev != NULL) {
970
971

	/* if we're at the end, wrap around to the beginning */
972
	while (open_files->prev != NULL) {
973
974
975
	    open_files = open_files->prev;

#ifdef DEBUG
976
	    fprintf(stderr, "filename is %s\n", open_files->filename);
977
978
979
980
981
982
983
#endif

	}
    }

    load_open_file();

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

987
988
989
990
991
992
993
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

994
995
996
/* This function is used by the shortcut list. */
int open_nextfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
997
    return open_nextfile(0);
998
999
}

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

1009
    if (open_files == NULL)
1010
1011
	return 1;

Chris Allegretta's avatar
Chris Allegretta committed
1012
1013
1014
    /* 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 */
1015
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
1016
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1017

1018
    tmp = open_files;
1019
1020
    if (open_nextfile(1)) {
	if (open_prevfile(1))
1021
1022
1023
	    return 1;
    }

1024
1025
    unlink_opennode(tmp);
    delete_opennode(tmp);
1026
1027
1028
1029
1030

    shortcut_init(0);
    display_main_list();
    return 0;
}
1031
#endif /* MULTIBUFFER */
1032

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

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

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

1058
    if (d_here != NULL) {
1059
1060

	align(&d_here);
1061
	if (strcmp(d_here, "/")) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1062
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1063
1064
	    strcat(d_here, "/");
	}
1065
1066
1067
1068
1069

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

1072
	expanded_origpath = real_dir_from_tilde(origpath);
1073
	/* save the value of origpath in both d_there and d_there_file */
1074
1075
1076
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1077

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

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

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

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

		free(d_there);

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

		align(&d_there);
1129
		if (d_there != NULL) {
1130
1131
1132
1133

		    /* 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
1134
			d_there = charealloc(d_there, strlen(d_there) + 2);
1135
1136
			strcat(d_there, "/");
		    }
1137
1138
1139
		}
		else
		    return NULL;
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
	    }

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

1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
	/* 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);
	}
1161
1162
1163
1164
1165
1166
1167
1168
1169

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

    return newpath;
}
1170
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1171
1172
1173

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

1184
    /* if get_full_path() failed, return NULL */
1185
    if (full_path == NULL)
1186
	return NULL;
1187
1188
1189

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

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

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

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

1230
1231
1232
    /* 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 */
1233
    if (full_tempdir == NULL && dirname != NULL)
1234
	full_tempdir = check_writable_directory(dirname);
1235
1236
1237

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1238
    if (full_tempdir == NULL)
1239
	full_tempdir = check_writable_directory(P_tmpdir);
1240
1241

    /* if P_tmpdir didn't work, use /tmp instead */
1242
    if (full_tempdir == NULL) {
1243
1244
1245
1246
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1247
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1248
1249

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

    free(full_tempdir);
    return NULL;
1265
1266
}
#endif /* !DISABLE_SPELLER */
1267
1268

#ifndef DISABLE_OPERATINGDIR
1269
1270
1271
1272
1273
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1274
    if (operating_dir == NULL)
1275
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1276

1277
1278
1279
    full_operating_dir = get_full_path(operating_dir);

    /* If get_full_path() failed or the operating directory is
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1280
     * inaccessible, unset operating_dir. */
1281
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1282
1283
1284
1285
1286
1287
1288
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

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

1300
1301
1302
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1303

1304
    /* If no operating directory is set, don't bother doing anything. */
1305
    if (operating_dir == NULL)
1306
	return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1307

1308
    assert(full_operating_dir != NULL);
1309
1310

    fullpath = get_full_path(currpath);
1311
1312
1313
1314
1315
1316
1317
1318

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

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

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

    /* Otherwise, we're still inside it. */
1336
    return retval;
1337
}
1338
1339
#endif

1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
#ifndef NANO_SMALL
void init_backup_dir(void)
{
    char *full_backup_dir;

    if (backup_dir == NULL)
	return;

    full_backup_dir = get_full_path(backup_dir);

    /* If get_full_path() failed or the backup directory is
     * inaccessible, unset backup_dir. */
    if (full_backup_dir == NULL ||
	full_backup_dir[strlen(full_backup_dir) - 1] != '/') {
	free(full_backup_dir);
	free(backup_dir);
	backup_dir = NULL;
    } else {
	free(backup_dir);
	backup_dir = full_backup_dir;
    }
}
#endif

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

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

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

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

1443
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1444

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

1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
    anyexists = lstat(realname, &lst) != -1;
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
    /* If NOFOLLOW_SYMLINKS is set, it doesn't make sense to prepend or
     * append to a symlink.  Here we warn about the contradiction. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode)) {
	statusbar(_("Cannot prepend or append to a symlink with --nofollow set."));
	goto cleanup_and_exit;
    }

1465
    /* Save the state of file at the end of the symlink (if there is
1466
1467
     * one). */
    realexists = stat(realname, &st) != -1;
1468

1469
1470
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1471
1472
1473
1474
1475
1476
1477
1478
     * temporary, and the file already exists.  Furthermore, if we
     * aren't appending, prepending, or writing a selection, we backup
     * only if the file has not been modified by someone else since nano
     * opened it. */
    if (ISSET(BACKUP_FILE) && !tmp && realexists != 0 &&
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1479
	FILE *backup_file;
1480
	char *backupname;
1481
	struct utimbuf filetime;
1482
	int copy_status;
1483

1484
	/* Save the original file's access and modification times. */
1485
1486
1487
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

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

1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
	/* If backup_dir is set, we set backupname to
	 * backup_dir/backupname~, where backupnae is the canonicalized
	 * absolute pathname of realname with every '/' replaced with a
	 * '!'.  This means that /home/foo/file is backed up in
	 * backup_dir/!home!foo!file~. */
	if (backup_dir != NULL) {
	    char *canon_realname = get_full_path(realname);
	    size_t i;

	    if (canon_realname == NULL)
		/* If get_full_path() failed, we don't have a
		 * canonicalized absolute pathname, so just use the
		 * filename portion of the pathname.  We use tail() so
		 * that e.g. ../backupname will be backed up in
		 * backupdir/backupname~ instead of
		 * backupdir/../backupname~. */
		canon_realname = mallocstrcpy(NULL, tail(realname));
	    else {
		for (i = 0; canon_realname[i] != '\0'; i++) {
		    if (canon_realname[i] == '/')
			canon_realname[i] = '!';
		}
	    }

	    backupname = charalloc(strlen(backup_dir) +
		strlen(canon_realname) + 2);
	    sprintf(backupname, "%s%s~", backup_dir, canon_realname);
	    free(canon_realname);
	} else {
	    backupname = charalloc(strlen(realname) + 2);
	    sprintf(backupname, "%s~", realname);
	}
1528

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

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

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

1566
1567
1568
1569
1570
1571
    /* If NOFOLLOW_SYMLINKS and the file is a link, we aren't doing
     * prepend or append.  So we delete the link first, and just
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1572
	goto cleanup_and_exit;
1573
    }
1574

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

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

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

1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
	fd_source = open(realname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
	}
	if (f_source == NULL) {
	    statusbar(_("Error reading %s: %s"), realname, strerror(errno));
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

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

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

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

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

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

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

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

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

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

1661
	if (size < data_len) {
1662
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1663
	    fclose(f);
1664
	    goto cleanup_and_exit;
1665
	}
1666
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1667
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1668
1669
1670
1671
1672
	    if (putc('\r', f) == EOF) {
		statusbar(_("Error writing %s: %s"), realname, strerror(errno));
		fclose(f);
		goto cleanup_and_exit;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1673
1674

	if (!ISSET(MAC_FILE))
1675
#endif
1676
1677
1678
1679
1680
	    if (putc('\n', f) == EOF) {
		statusbar(_("Error writing %s: %s"), realname, strerror(errno));
		fclose(f);
		goto cleanup_and_exit;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1681
1682
1683
1684
1685

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

1686
    /* If we're prepending, open the temp file, and append it to f. */
1687
    if (append == 2) {
1688
1689
1690
1691
1692
1693
1694
1695
	int fd_source;
	FILE *f_source = NULL;

	fd_source = open(tempname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
1696
	}
1697
	if (f_source == NULL) {
1698
1699
	    statusbar(_("Error reading %s: %s"), tempname, strerror(errno));
	    fclose(f);
1700
	    goto cleanup_and_exit;
1701
1702
	}

1703
1704
1705
	if (copy_file(f_source, f) == -1
		|| unlink(tempname) == -1) {
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1706
	    goto cleanup_and_exit;
1707
	}
1708
1709
1710
1711
    } else if (fclose(f) == EOF) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	unlink(tempname);
	goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1712
    }
1713

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

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

    retval = 1;

  cleanup_and_exit:
    free(realname);
1738
    free(tempname);
1739
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1740
1741
}

1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
#ifndef NANO_SMALL
/* Write a marked selection from a file out.  First, set fileage and
 * filebot as the top and bottom of the mark, respectively.  Then call
 * write_file() with the values of name, temp, append, and nonamechange.
 * Finally, set fileage and filebot back to their old values and
 * return. */
int write_marked(const char *name, int tmp, int append, int
	nonamechange)
{
    int retval = -1;
    filestruct *fileagebak = fileage;
    filestruct *filebotbak = filebot;
    int oldmod = ISSET(MODIFIED);
	/* write_file() unsets the MODIFIED flag. */
    size_t topx;
	/* The column of the beginning of the mark. */
    char origchar;
	/* We replace the character at the end of the mark with '\0'.
	 * We save the original character, to restore it. */
    char *origcharloc;
	/* The location of the character we nulled. */

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

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

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

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

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

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

1805
int do_writeout(const char *path, int exiting, int append)
Chris Allegretta's avatar
Chris Allegretta committed
1806
1807
{
    int i = 0;
1808
1809
1810
#ifdef NANO_EXTRA
    static int did_cred = 0;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1811

1812
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1813
    currshortcut = writefile_list;
1814
1815
#endif

1816
    answer = mallocstrcpy(answer, path);
Chris Allegretta's avatar
Chris Allegretta committed
1817

1818
    if (exiting && ISSET(TEMP_OPT)) {
1819
	if (filename[0] != '\0') {
1820
	    i = write_file(answer, 0, 0, 0);
1821
1822
	    display_main_list();
	    return i;
1823
	} else {
Chris Allegretta's avatar
Chris Allegretta committed
1824
1825
1826
1827
1828
	    UNSET(TEMP_OPT);
	    do_exit();

	    /* They cancelled, abort quit */
	    return -1;
1829
	}
Chris Allegretta's avatar
Chris Allegretta committed
1830
1831
1832
    }

    while (1) {
1833
#ifndef NANO_SMALL
1834
1835
	const char *formatstr, *backupstr;

1836
1837
1838
1839
1840
1841
1842
	if (ISSET(MAC_FILE))
	   formatstr = _(" [Mac Format]");
	else if (ISSET(DOS_FILE))
	   formatstr = _(" [DOS Format]");
	else
	   formatstr = "";

1843
1844
1845
1846
1847
	if (ISSET(BACKUP_FILE))
	   backupstr = _(" [Backup]");
	else
	   backupstr = "";

1848
	/* Be nice to the translation folks */
1849
	if (ISSET(MARK_ISSET) && !exiting) {
1850
	    if (append == 2)
1851
		i = statusq(1, writefile_list, "", 0,
1852
		    "%s%s%s", _("Prepend Selection to File"), formatstr, backupstr);
Chris Allegretta's avatar
Chris Allegretta committed
1853
	    else if (append == 1)
1854
		i = statusq(1, writefile_list, "", 0,
1855
		    "%s%s%s", _("Append Selection to File"), formatstr, backupstr);
1856
	    else
1857
		i = statusq(1, writefile_list, "", 0,
1858
1859
		    "%s%s%s", _("Write Selection to File"), formatstr, backupstr);
	} else {
1860
	    if (append == 2)
1861
		i = statusq(1, writefile_list, answer, 0,
1862
		    "%s%s%s", _("File Name to Prepend to"), formatstr, backupstr);
Chris Allegretta's avatar
Chris Allegretta committed
1863
	    else if (append == 1)
1864
		i = statusq(1, writefile_list, answer, 0,
1865
		    "%s%s%s", _("File Name to Append to"), formatstr, backupstr);
1866
	    else
1867
		i = statusq(1, writefile_list, answer, 0,
1868
		    "%s%s%s", _("File Name to Write"), formatstr, backupstr);
1869
	}
1870
1871
#else
	if (append == 2)
Chris Allegretta's avatar
Chris Allegretta committed
1872
	    i = statusq(1, writefile_list, answer,
1873
		"%s", _("File Name to Prepend to"));
Chris Allegretta's avatar
Chris Allegretta committed
1874
1875
	else if (append == 1)
	    i = statusq(1, writefile_list, answer,
1876
1877
		"%s", _("File Name to Append to"));
	else
Chris Allegretta's avatar
Chris Allegretta committed
1878
	    i = statusq(1, writefile_list, answer,
1879
1880
		"%s", _("File Name to Write"));
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1881

1882
1883
1884
1885
1886
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
	    return 0;
	}
Chris Allegretta's avatar
Chris Allegretta committed
1887

1888
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1889
	if (i == NANO_TOFILES_KEY) {
1890
	    char *tmp = do_browse_from(answer);
1891

1892
	    currshortcut = writefile_list;
1893
1894
1895
1896
	    if (tmp == NULL)
		continue;
	    free(answer);
	    answer = tmp;
1897
	} else
1898
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1899
#ifndef NANO_SMALL
1900
1901
1902
	if (i == TOGGLE_DOS_KEY) {
	    UNSET(MAC_FILE);
	    TOGGLE(DOS_FILE);
1903
	    continue;
1904
1905
1906
	} else if (i == TOGGLE_MAC_KEY) {
	    UNSET(DOS_FILE);
	    TOGGLE(MAC_FILE);
1907
	    continue;
1908
1909
	} else if (i == TOGGLE_BACKUP_KEY) {
	    TOGGLE(BACKUP_FILE);
1910
1911
	    continue;
	} else
1912
#endif /* !NANO_SMALL */
1913
1914
1915
1916
1917
1918
1919
	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
1920

Chris Allegretta's avatar
Chris Allegretta committed
1921
#ifdef DEBUG
1922
	fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1923
#endif
1924
1925

#ifdef NANO_EXTRA
1926
	if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy")
1927
		&& !did_cred) {
1928
1929
1930
1931
	    do_credits();
	    did_cred = 1;
	    return -1;
	}
1932
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1933
	if (append == 0 && strcmp(answer, filename)) {
1934
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1935

1936
	    if (!stat(answer, &st)) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1937
		i = do_yesno(0, _("File exists, OVERWRITE ?"));
1938
1939
1940
1941
1942
1943
		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
1944
		) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1945
		i = do_yesno(0, _("Save file under DIFFERENT NAME ?"));
1946
1947
1948
		if (i == 0 || i == -1)
		    continue;
	    }
1949
	}
Chris Allegretta's avatar
Chris Allegretta committed
1950

1951
#ifndef NANO_SMALL
1952
1953
	/* Here's where we allow the selected text to be written to
	 * a separate file. */
1954
1955
1956
	if (ISSET(MARK_ISSET) && !exiting)
	    i = write_marked(answer, 0, append, 1);
	else
1957
#endif /* !NANO_SMALL */
1958
	    i = write_file(answer, 0, append, 0);
1959

1960
#ifdef ENABLE_MULTIBUFFER
1961
1962
1963
1964
	/* If we're not about to exit, update the current entry in
	   the open_files structure. */
	if (!exiting)
	    add_open_file(1);
1965
#endif
1966
1967
1968
	display_main_list();
	return i;
    } /* while (1) */
Chris Allegretta's avatar
Chris Allegretta committed
1969
1970
1971
1972
}

int do_writeout_void(void)
{
1973
    return do_writeout(filename, 0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
1974
}
Chris Allegretta's avatar
Chris Allegretta committed
1975

1976
/* Return a malloc()ed string containing the actual directory, used
1977
1978
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1979
{
Chris Allegretta's avatar
Chris Allegretta committed
1980
    char *dirtmp = NULL;
1981

1982
    if (buf[0] == '~') {
1983
1984
1985
1986
1987
1988
1989
	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++)
	    ;

1990
1991
	/* Determine home directory using getpwuid() or getpwent(),
	   don't rely on $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
1992
1993
1994
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
1995
1996
1997
1998
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
			strncmp(userdata->pw_name, buf + 1, i - 1));
Chris Allegretta's avatar
Chris Allegretta committed
1999
2000
	}
	endpwent();
2001

2002
	if (userdata != NULL) {	/* User found */
2003
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2004
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
2005
	}
2006
    }
2007

2008
2009
2010
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

2011
    return dirtmp;
2012
2013
}

Chris Allegretta's avatar
Chris Allegretta committed
2014
#ifndef DISABLE_TABCOMP
2015
2016
2017
/* 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
2018
int append_slash_if_dir(char *buf, int *lastwastab, int *place)
2019
{
2020
    char *dirptr = real_dir_from_tilde(buf);
2021
    struct stat fileinfo;
2022
    int ret = 0;
2023

2024
    assert(dirptr != buf);
2025

2026
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
2027
	strncat(buf, "/", 1);
2028
	(*place)++;
2029
	/* now we start over again with # of tabs so far */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2030
	*lastwastab = 0;
2031
	ret = 1;
2032
2033
    }

2034
    free(dirptr);
2035
    return ret;
2036
}
Chris Allegretta's avatar
Chris Allegretta committed
2037
2038

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

2063
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2064
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2065

2066
    strcat(buf, "*");
2067

2068
    while ((userdata = getpwent()) != NULL) {
2069

2070
	if (check_wildcard_match(userdata->pw_name, &buf[1]) == TRUE) {
2071
2072
2073
2074

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

2076
2077
2078
2079
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2080
2081
	    if (operating_dir != NULL) {
		if (check_operating_dir(userdata->pw_dir, 1) != 0)
2082
2083
2084
2085
		    continue;
	    }
#endif

2086
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2087
2088
2089
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
	    ++*num_matches;
2090

2091
2092
2093
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2094
	}
2095
2096
    }
    endpwent();
2097

2098
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2099
2100
2101
2102
2103
2104
2105
}

/* 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
2106
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2107
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2108
2109
2110
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2111
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2112
2113
2114
2115

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

2116
    /* Okie, if there's a / in the buffer, strip out the directory part */
2117
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2118
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2119
2120
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2121
	    tmp--;
2122

Chris Allegretta's avatar
Chris Allegretta committed
2123
2124
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2125
2126
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2127

Chris Allegretta's avatar
Chris Allegretta committed
2128
    } else {
2129
2130

#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2131
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
2132
#else
2133
	/* The better, but apparently segfault-causing way */
Chris Allegretta's avatar
Chris Allegretta committed
2134
	if ((dirname = getcwd(NULL, 0)) == NULL)
2135
#endif /* PATH_MAX */
Chris Allegretta's avatar
Chris Allegretta committed
2136
2137
2138
2139
2140
2141
	    return matches;
	else
	    tmp = buf;
    }

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

Chris Allegretta's avatar
Chris Allegretta committed
2147
2148
2149
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2150
2151

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


Chris Allegretta's avatar
Chris Allegretta committed
2158
    dir = opendir(dirname);
2159
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2160
2161
2162
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2163
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2164
2165
2166
2167
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2168
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2169
2170
2171
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
2172
2173
2174
2175

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2176
2177
2178
2179
2180
2181
2182
2183

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

2184
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2185
2186
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2187
2188
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2189
		if (check_operating_dir(tmp2, 1) != 0) {
2190
2191
2192
2193
2194
2195
2196
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2197
	    tmp2 = NULL;
2198
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2199
2200
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2201
	    ++*num_matches;
2202
2203
2204
2205

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2206
2207
	}
    }
2208
2209
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2210

2211
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2212
2213
}

Chris Allegretta's avatar
Chris Allegretta committed
2214
2215
/* 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
2216
char *input_tab(char *buf, int place, int *lastwastab, int *newplace, int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2217
2218
{
    /* Do TAB completion */
2219
    static int num_matches = 0, match_matches = 0;
2220
    static char **matches = (char **)NULL;
2221
    int pos = place, i = 0, col = 0, editline = 0;
2222
    int longestname = 0, is_dir = 0;
2223
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2224

2225
2226
    *list = 0;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2227
    if (*lastwastab == FALSE) {
2228
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2229

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2230
	*lastwastab = 1;
2231

Chris Allegretta's avatar
Chris Allegretta committed
2232
2233
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2234
2235
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2236

2237
2238
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2239
2240

	/* skip any leading white space */
2241
	while (*tmp && isspace((int)*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2242
2243
2244
2245
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2246
2247
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2248
	    free(matches);
2249
	    matches = (char **)NULL;
2250
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2251
2252
2253
2254
2255
	}

	/* 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
2256
2257
2258
2259
2260
	/* 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. */
2261
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2262
	    buf = mallocstrcpy(buf, tmp);
2263
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2264
	}
2265
2266
2267
2268
2269
2270
	/* 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
2271
2272
2273

	/* Try to match everything in the current working directory that
	 * matches.  */
2274
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2275
2276
2277
	    matches = cwd_tab_completion(tmp, &num_matches);

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

2280
2281
2282
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2283
	/* Did we find exactly one match? */
2284
	switch (num_matches) {
2285
	case 0:
2286
	    blank_edit();
2287
	    wrefresh(edit);
2288
2289
	    break;
	case 1:
2290

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

2293
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2294
2295
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2296
		tmp++;
2297
	    } else
2298
2299
		tmp = buf;

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

2303
	    if (is_dir != 0)
2304
		break;
2305
2306

	    copyto = tmp;
2307
2308
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2309
2310
		tmp++;

2311
	    /* write out the matched name */
2312
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2313
2314
	    *newplace += strlen(matches[0]) - pos;

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

2322
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2323
	    append_slash_if_dir(buf, lastwastab, newplace);
2324

2325
2326
	    break;
	default:
2327
	    /* Check to see if all matches share a beginning, and, if so,
2328
	       tack it onto buf and then beep */
2329

2330
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2331
2332
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2333
		tmp++;
2334
	    } else
2335
2336
		tmp = buf;

2337
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2338
		 pos <= strlen(matches[0]); pos++)
2339
2340
		tmp++;

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

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

2374
	    editline = 0;
2375

2376
2377
2378
2379
2380
2381
2382
2383
	    /* 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;

2384
	    foo = charalloc(longestname + 5);
2385

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

2389
		/* make each filename shown be the same length as the longest
2390
		   filename, with two spaces at the end */
2391
2392
2393
2394
2395
2396
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

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

2422
2423
2424
2425
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
    if (*list == 0)
	edit_refresh();
2426
2427
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2428
}
2429
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2430

2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
#if !defined(DISABLE_BROWSER) || !defined(NANO_SMALL)
/* Only print the last part of a path; isn't there a shell
 * command for this? */
const char *tail(const char *foo)
{
    const char *tmp = foo + strlen(foo);

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

    if (*tmp == '/')
	tmp++;

    return tmp;
}
#endif

2448
#ifndef DISABLE_BROWSER
2449
/* Our sort routine for file listings -- sort directories before
2450
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2451
2452
int diralphasort(const void *va, const void *vb)
{
2453
2454
2455
2456
    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
2457

2458
    if (aisdir != 0 && bisdir == 0)
2459
	return -1;
2460
    if (aisdir == 0 && bisdir != 0)
2461
	return 1;
2462

2463
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2464
2465
}

2466
/* Free our malloc()ed memory */
Chris Allegretta's avatar
Chris Allegretta committed
2467
2468
void free_charptrarray(char **array, int len)
{
2469
2470
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2471
2472
2473
    free(array);
}

2474
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2475
2476
void striponedir(char *foo)
{
2477
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2478

2479
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2480
    /* Don't strip the root dir */
2481
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2482
2483
	return;

2484
2485
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2486
    if (*tmp == '/')
2487
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2488
2489
2490
2491
2492

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

    if (tmp != foo)
2493
2494
2495
2496
2497
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2498
    }
Chris Allegretta's avatar
Chris Allegretta committed
2499
2500
}

2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
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;
}

2511
/* Initialize the browser code, including the list of files in *path */
2512
char **browser_init(const char *path, int *longest, int *numents)
2513
2514
2515
{
    DIR *dir;
    struct dirent *next;
2516
    char **filelist;
2517
    int i = 0;
2518
    size_t path_len;
2519
2520

    dir = opendir(path);
2521
    if (dir == NULL)
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
	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
2535
    filelist = (char **)nmalloc(*numents * sizeof (char *));
2536

2537
2538
2539
2540
    if (!strcmp(path, "/"))
	path = "";
    path_len = strlen(path);

2541
2542
2543
2544
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;

2545
2546
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2547
2548
	i++;
    }
2549
    closedir(dir);
2550
2551
2552
2553
2554
2555
2556

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2557
/* Our browser function.  inpath is the path to start browsing from */
2558
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2559
2560
2561
2562
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2563
2564
2565
    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;
2566
    char **filelist = (char **)NULL;
2567
#ifndef DISABLE_MOUSE
2568
2569
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2570

2571
2572
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2573
2574
2575
    /* 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
2576
2577
2578
2579
2580
    if (path != NULL && strcmp(path, inpath)) {
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2581
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2582
    if (path == NULL)
2583
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2584
2585

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

2588
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2589
2590
2591
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2592
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2593
2594
2595
2596
2597
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2598
2599

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2600
    do {
2601
2602
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2603
2604
2605

	blank_statusbar_refresh();

2606
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2607
	currshortcut = browser_list;
2608
2609
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2610
2611
 	editline = 0;
	col = 0;
2612
	    
2613
	/* Compute line number we're on now, so we don't divide by zero later */
2614
2615
2616
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2617
2618

	switch (kbinput) {
2619

2620
#ifndef DISABLE_MOUSE
2621
	case KEY_MOUSE:
2622
	    if (getmouse(&mevent) == ERR)
2623
		return retval;
2624
2625
2626
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2627
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2628
2629
2630
2631
		int selectedbackup = selected;

		mevent.y -= 2;

2632
2633
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2634
		selected = (lineno / editwinrows) * editwinrows * width
2635
2636
2637
2638
2639
2640
			+ 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--;
2641
2642
2643

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2644
		if (selected > numents - 1)
2645
		    selected = numents - 1;
2646
		else if (selectedbackup == selected)
2647
2648
2649
2650
		    ungetch('s');	/* Unget the 'select' key */
	    } else	/* Must be clicking a shortcut */
		do_mouse();

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

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

2710
2711
2712
2713
2714
	    if (stat(filelist[selected], &st) == -1) {
		statusbar(_("Can't open \"%s\": %s"), filelist[selected], strerror(errno));
		beep();
		break;
	    }
2715

2716
2717
2718
2719
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2720
2721
	    }

2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
	    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
2732
		}
2733
2734
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2735

2736
2737
2738
2739
2740
	    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
2741
	    }
2742
2743
2744
2745
2746
2747
2748

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

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

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

2768
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2769

2770
2771
2772
	    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
2773
2774
	    }

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

	    /* Start over again with the new path value */
2791
2792
	    free_charptrarray(filelist, numents);
	    free(foo);
2793
2794
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2795
2796
	    return do_browser(path);

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

Rocco Corsi's avatar
   
Rocco Corsi committed
2809
2810
	blank_edit();

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

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

Chris Allegretta's avatar
Chris Allegretta committed
2862
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2863
2864
2865
2866
2867
	    waddstr(edit, "  ");
	    col += 2;

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

Chris Allegretta's avatar
Chris Allegretta committed
2883
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2884
2885
2886
2887
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2888

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

2899
2900
2901
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2902

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

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

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

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


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

    /* assume do_rcfile has reported missing home dir */

    if (homenv != NULL || userage != NULL) {
2958
	hist = fopen(nanohist, "r");
2959
	if (hist == NULL) {
2960
2961
2962
            if (errno != ENOENT) {
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2963
		rcfile_error(_("Unable to open ~/.nano_history file, %s"), strerror(errno));
2964
	    }
2965
2966
2967
2968
2969
	    free(nanohist);
	} else {
	    buf = charalloc(1024);
	    while (fgets(buf, 1023, hist) != 0) {
		ptr = buf;
2970
		while (*ptr != '\n' && *ptr != '\0' && ptr < buf + 1023)
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
		    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
2990
    const struct passwd *userage = NULL;
2991
    char *nanohist = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2992
    char *homenv = getenv("HOME");
2993
2994
2995
2996
2997
2998
2999
    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
3000
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3001
	nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
3002
3003
3004
3005
	sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3006
	nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
3007
	sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
3008
3009
3010
    }

    if (homenv != NULL || userage != NULL) {
3011
	hist = fopen(nanohist, "wb");
3012
	if (hist == NULL) {
3013
3014
3015
3016
3017
3018
	    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
3019
		h->data = charealloc(h->data, strlen(h->data) + 2);
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
		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
3031
		h->data = charealloc(h->data, strlen(h->data) + 2);
3032
3033
3034
3035
3036
3037
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
		    rcfile_msg(_("Unable to write ~/.nano_history file, %s"), strerror(errno));
		    goto come_from;
		}
	    }
3038
  come_from:
3039
3040
3041
3042
3043
3044
	    fclose(hist);
	}
	free(nanohist);
    }
}
#endif /* ENABLE_NANORC */
Chris Allegretta's avatar
Chris Allegretta committed
3045
#endif /* !NANO_SMALL */