files.c 76 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-2005 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
24
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
25

Chris Allegretta's avatar
Chris Allegretta committed
26
27
28
29
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
#include <sys/wait.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
#include <ctype.h>
35
#include <pwd.h>
Chris Allegretta's avatar
Chris Allegretta committed
36
#include <assert.h>
Chris Allegretta's avatar
Chris Allegretta committed
37
#include "proto.h"
Chris Allegretta's avatar
Chris Allegretta committed
38

39
40
static file_format fmt = NIX_FILE;
	/* The format of the current file. */
41

Chris Allegretta's avatar
Chris Allegretta committed
42
43
44
/* What happens when there is no file to open? aiee! */
void new_file(void)
{
45
    fileage = make_new_node(NULL);
46
    fileage->data = mallocstrcpy(NULL, "");
Chris Allegretta's avatar
Chris Allegretta committed
47
48
49
    filebot = fileage;
    edittop = fileage;
    current = fileage;
50
    current_x = 0;
Chris Allegretta's avatar
Chris Allegretta committed
51
    totlines = 1;
52
    totsize = 0;
53

54
55
#ifdef ENABLE_COLOR
    update_color();
56
    if (!ISSET(NO_COLOR_SYNTAX))
57
	edit_refresh();
58
#endif
Chris Allegretta's avatar
Chris Allegretta committed
59
60
}

61
62
63
64
65
66
/* We make a new line of text from buf.  buf is length len.  If
 * first_line_ins is TRUE, then we put the new line at the top of the
 * file.  Otherwise, we assume prev is the last line of the file, and
 * put our line after prev. */
filestruct *read_line(char *buf, filestruct *prev, bool *first_line_ins,
	size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
67
{
68
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
69

70
71
    /* Convert nulls to newlines.  len is the string's real length
     * here. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
72
73
    unsunder(buf, len);

74
75
76
    assert(strlen(buf) == len);

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

78
#ifndef NANO_SMALL
79
80
    /* If it's a DOS file (CR LF), and file conversion isn't disabled,
     * strip out the CR part. */
81
    if (!ISSET(NO_CONVERT) && len > 0 && buf[len - 1] == '\r')
82
	fileptr->data[len - 1] = '\0';
83
84
#endif

85
86
87
    if (*first_line_ins || fileage == NULL) {
	/* Special case: we're inserting with the cursor on the first
	 * line. */
Chris Allegretta's avatar
Chris Allegretta committed
88
89
90
	fileptr->prev = NULL;
	fileptr->next = fileage;
	fileptr->lineno = 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
91
	if (*first_line_ins == TRUE) {
92
	    *first_line_ins = FALSE;
93
	    /* If we're inserting into the first line of the file, then
94
95
	     * we want to make sure that our edit buffer stays on the
	     * first line and that fileage stays up to date. */
96
97
98
	    edittop = fileptr;
	} else
	    filebot = fileptr;
Chris Allegretta's avatar
Chris Allegretta committed
99
	fileage = fileptr;
100
101
    } else {
	assert(prev != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
102

Chris Allegretta's avatar
Chris Allegretta committed
103
104
105
106
107
108
109
110
111
	fileptr->prev = prev;
	fileptr->next = NULL;
	fileptr->lineno = prev->lineno + 1;
	prev->next = fileptr;
    }

    return fileptr;
}

112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/* Load a file into the edit buffer.  This takes data from the file
 * struct. */
void load_file(void)
{
    current = fileage;

#ifdef ENABLE_MULTIBUFFER
    /* Add a new entry to the open_files structure. */
    add_open_file(FALSE);

    /* Reinitialize the shortcut list. */
    shortcut_init(FALSE);
#endif
}

void read_file(FILE *f, const char *filename)
Chris Allegretta's avatar
Chris Allegretta committed
128
{
129
130
    size_t num_lines = 0;
	/* The number of lines in the file. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
131
132
    size_t num_chars;
	/* The number of characters in the file. */
133
134
135
136
137
138
139
140
    size_t len = 0;
	/* The length of the current line of the file. */
    size_t i = 0;
	/* The position in the current line of the file. */
    size_t bufx = 128;
	/* The size of each chunk of the file that we read. */
    char input = '\0';
	/* The current input character. */
Chris Allegretta's avatar
Chris Allegretta committed
141
    char *buf;
142
143
144
145
146
147
148
149
	/* The buffer where we store chunks of the file. */
    filestruct *fileptr = current;
	/* The current line of the file. */
    bool first_line_ins = FALSE;
	/* Whether we're inserting with the cursor on the first line. */
    int input_int;
	/* The current value we read from the file, whether an input
	 * character or EOF. */
150
#ifndef NANO_SMALL
151
    int format = 0;
152
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
153
#endif
Chris Allegretta's avatar
Chris Allegretta committed
154

155
    buf = charalloc(bufx);
156
    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
157

158
159
    if (current != NULL) {
	if (current == fileage)
160
	    first_line_ins = TRUE;
161
162
	else
	    fileptr = current->prev;
Chris Allegretta's avatar
Chris Allegretta committed
163
    }
164

165
166
    /* For the assertion in read_line(), it must be true that if current
     * is NULL, then so is fileage. */
167
168
    assert(current != NULL || fileage == NULL);

169
#ifndef NANO_SMALL
170
171
172
    /* We don't know which file format we have yet, so assume it's a
     * *nix file for now. */
    fmt = NIX_FILE;
173
174
#endif

175
    /* Read the entire file into the file struct. */
176
    while ((input_int = getc(f)) != EOF) {
177
	input = (char)input_int;
Chris Allegretta's avatar
Chris Allegretta committed
178

179
180
	/* If it's a *nix file (LF) or a DOS file (CR LF), and file
	 * conversion isn't disabled, handle it! */
181
	if (input == '\n') {
182
#ifndef NANO_SMALL
183
184
	    /* If there's a CR before the LF, set format to DOS if we
	     * currently think this is a *nix file, or to both if we
185
186
	     * currently think it's a Mac file. */
	    if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r' &&
187
188
		(format == 0 || format == 2))
		format++;
189
190
191
192
#endif

	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
193

194
195
	    /* Reset the line length in preparation for the next
	     * line. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
196
197
	    len = 0;

198
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
199
	    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
200
	    i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
201
#ifndef NANO_SMALL
202
203
	/* If it's a Mac file (CR without an LF), and file conversion
	 * isn't disabled, handle it! */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
204
205
	} else if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r') {

206
207
208
209
210
	    /* If we currently think the file is a *nix file, set format
	     * to Mac.  If we currently think the file is a DOS file,
	     * set format to both DOS and Mac. */
	    if (format == 0 || format == 1)
		format += 2;
211
212
213

	    /* Read in the line properly. */
	    fileptr = read_line(buf, fileptr, &first_line_ins, len);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
214

215
216
217
	    /* Reset the line length in preparation for the next line.
	     * Since we've already read in the next character, reset it
	     * to 1 instead of 0. */
218
	    len = 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
219

Chris Allegretta's avatar
Chris Allegretta committed
220
	    num_lines++;
221
	    buf[0] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
222
	    buf[1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
223
224
	    i = 1;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
225
	} else {
226
227
	    /* Calculate the total length of the line.  It might have
	     * nulls in it, so we can't just use strlen() here. */
228
229
	    len++;

Chris Allegretta's avatar
Chris Allegretta committed
230
	    /* Now we allocate a bigger buffer 128 characters at a time.
231
232
233
	     * If we allocate a lot of space for one line, we may indeed
	     * have to use a buffer this big later on, so we don't
	     * decrease it at all.  We do free it at the end, though. */
Chris Allegretta's avatar
Chris Allegretta committed
234
235
	    if (i >= bufx - 1) {
		bufx += 128;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
236
		buf = charealloc(buf, bufx);
Chris Allegretta's avatar
Chris Allegretta committed
237
	    }
238

239
	    buf[i] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
240
	    buf[i + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
241
242
	    i++;
	}
243
244
    }

245
246
    /* This conditional duplicates previous read_byte() behavior.
     * Perhaps this could use some better handling. */
247
248
249
    if (ferror(f))
	nperror(filename);
    fclose(f);
Chris Allegretta's avatar
Chris Allegretta committed
250

251
#ifndef NANO_SMALL
252
253
254
    /* 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. */
    if (len == 0 && !ISSET(NO_CONVERT) && input == '\r') {
255
256
	len = 1;

257
258
	buf[0] = input;
	buf[1] = '\0';
259
260
    }
#endif
261

262
263
    /* Did we not get a newline and still have stuff to do? */
    if (len > 0) {
264
#ifndef NANO_SMALL
265
	/* If file conversion isn't disabled and the last character in
266
267
	 * this file is a CR, set format to Mac if we currently think
	 * the file is a *nix file, or to both DOS and Mac if we
268
269
	 * currently think the file is a DOS file. */
	if (!ISSET(NO_CONVERT) && buf[len - 1] == '\r' &&
270
271
		(format == 0 || format == 1))
	    format += 2;
272
273
#endif

274
275
276
277
	/* Read in the last line properly. */
	fileptr = read_line(buf, fileptr, &first_line_ins, len);
	num_lines++;
    }
278

279
280
281
282
    free(buf);

    /* If we didn't get a file and we don't already have one, make a new
     * file. */
283
    if (fileptr == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
284
	new_file();
Robert Siemborski's avatar
Robert Siemborski committed
285

286
    /* Did we try to insert a file of 0 bytes? */
287
288
289
290
291
292
293
294
295
296
    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();
297
	    totsize--;
298
	}
Chris Allegretta's avatar
Chris Allegretta committed
299
    }
300

301
302
303
    get_totals(fileage, filebot, NULL, &num_chars);
    totsize += num_chars;

304
#ifndef NANO_SMALL
305
    if (format == 3)
306
307
308
309
	statusbar(
		P_("Read %lu line (Converted from DOS and Mac format)",
		"Read %lu lines (Converted from DOS and Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
310
311
    else if (format == 2) {
	fmt = MAC_FILE;
312
313
314
	statusbar(P_("Read %lu line (Converted from Mac format)",
		"Read %lu lines (Converted from Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
315
316
    } else if (format == 1) {
	fmt = DOS_FILE;
317
318
319
	statusbar(P_("Read %lu line (Converted from DOS format)",
		"Read %lu lines (Converted from DOS format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
320
    } else
321
#endif
322
	statusbar(P_("Read %lu line", "Read %lu lines",
323
		(unsigned long)num_lines), (unsigned long)num_lines);
324

325
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
326
327
}

328
329
330
331
332
333
334
335
/* Open the file (and decide if it exists).  If newfie is TRUE, display
 * "New File" if the file is missing.  Otherwise, say "[filename] not
 * found".
 *
 * Return -2 if we say "New File".  Otherwise, -1 if the file isn't
 * opened, 0 otherwise.  The file might still have an error while
 * reading with a 0 return value.  *f is set to the opened file. */
int open_file(const char *filename, bool newfie, FILE **f)
Chris Allegretta's avatar
Chris Allegretta committed
336
337
338
339
{
    int fd;
    struct stat fileinfo;

340
    assert(f != NULL);
341

342
343
344
    if (filename == NULL || filename[0] == '\0' ||
	    stat(filename, &fileinfo) == -1) {
	if (newfie) {
Chris Allegretta's avatar
Chris Allegretta committed
345
	    statusbar(_("New File"));
346
	    return -2;
Chris Allegretta's avatar
Chris Allegretta committed
347
	}
348
349
	statusbar(_("\"%s\" not found"), filename);
	return -1;
350
351
352
    } 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! */
353
354
	statusbar(S_ISDIR(fileinfo.st_mode) ? _("\"%s\" is a directory")
		: _("File \"%s\" is a device file"), filename);
355
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
356
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
357
358
359
	statusbar(_("Error reading %s: %s"), filename, strerror(errno));
 	return -1;
     } else {
360
361
362
	/* File is A-OK.  Open it in binary mode for our own end-of-line
	 * character munging. */
	*f = fdopen(fd, "rb");
363
364

	if (*f == NULL) {
365
366
	    statusbar(_("Error reading %s: %s"), filename,
		strerror(errno));
367
	    close(fd);
368
369
	} else
	    statusbar(_("Reading File"));
Chris Allegretta's avatar
Chris Allegretta committed
370
    }
371
    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
372
373
}

374
/* This function will return the name of the first available extension
375
376
 * of a filename (starting with filename.save, then filename.save.1,
 * etc.).  Memory is allocated for the return value.  If no writable
377
 * extension exists, we return "". */
Chris Allegretta's avatar
Chris Allegretta committed
378
char *get_next_filename(const char *name)
379
{
380
    unsigned long i = 0;
381
382
    char *buf;
    size_t namelen = strlen(name);
383

384
    buf = charalloc(namelen + digits(ULONG_MAX) + 7);
385
    strcpy(buf, name);
386
387
    strcpy(buf + namelen, ".save");
    namelen += 5;
388

389
    while (TRUE) {
390
	struct stat fs;
391
392

	if (stat(buf, &fs) == -1)
Chris Allegretta's avatar
Chris Allegretta committed
393
	    return buf;
394
	if (i == ULONG_MAX)
395
396
397
	    break;

	i++;
398
	sprintf(buf + namelen, ".%lu", i);
399
400
    }

Chris Allegretta's avatar
Chris Allegretta committed
401
    /* We get here only if there is no possible save file. */
402
    null_at(&buf, 0);
403

404
405
406
    return buf;
}

407
408
#ifndef NANO_SMALL
void execute_command(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
409
{
410
411
412
413
414
415
416
#ifdef ENABLE_MULTIBUFFER
    if (ISSET(MULTIBUFFER)) {
	/* Update the current entry in the open_files structure. */
	add_open_file(TRUE);
	new_file();
	UNSET(MODIFIED);
	UNSET(MARK_ISSET);
417
    }
418
419
420
421
422
423
424
425
426
#endif /* ENABLE_MULTIBUFFER */
    open_pipe(command);
#ifdef ENABLE_MULTIBUFFER
    /* Add this new entry to the open_files structure. */
    if (ISSET(MULTIBUFFER))
	load_file();
#endif /* ENABLE_MULTIBUFFER */
}
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
427

428
429
430
431
/* name is a file name to open.  We make a new buffer if necessary, then
 * open and read the file. */
void load_buffer(const char *name)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
432
    bool new_buffer = (fileage == NULL
433
434
#ifdef ENABLE_MULTIBUFFER
	 || ISSET(MULTIBUFFER)
435
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
436
	);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
437
438
439
440
	/* new_buffer says whether we load into this buffer or a new
	 * one.  If new_buffer is TRUE, we display "New File" if the
	 * file is not found, and if it is found we set filename and add
	 * a new open_files entry. */
441
442
443
444
    FILE *f;
    int rc;
	/* rc == -2 means that the statusbar displayed "New File".  -1
	 * means that the open failed.  0 means success. */
445

446
447
#ifndef DISABLE_OPERATINGDIR
    if (check_operating_dir(name, FALSE)) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
448
449
	statusbar(_("Can't insert file from outside of %s"),
		operating_dir);
450
451
	return;
    }
452
453
#endif

454
455
456
#ifdef ENABLE_MULTIBUFFER
    /* Update the current entry in the open_files structure. */
    add_open_file(TRUE);
457
458
#endif

459
460
461
462
463
    rc = open_file(name, new_buffer, &f);

#ifdef ENABLE_MULTIBUFFER
    if (rc != -1 && ISSET(MULTIBUFFER)) {
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
464
#ifndef NANO_SMALL
465
	UNSET(MARK_ISSET);
Chris Allegretta's avatar
Chris Allegretta committed
466
#endif
467
    }
468
#endif
469
470
471
472
473
474
475

    if (rc != -1 && new_buffer) {
	filename = mallocstrcpy(filename, name);
	new_file();
    }

    if (rc == 0) {
476
477
	file_format fmt_save = fmt;

478
	read_file(f, filename);
479
480
481
482
483
484

	/* If we're not loading into a new buffer, preserve the file
	 * format. */
	if (!new_buffer)
	    fmt = fmt_save;

Chris Allegretta's avatar
Chris Allegretta committed
485
#ifndef NANO_SMALL
486
	stat(filename, &originalfilestat);
Chris Allegretta's avatar
Chris Allegretta committed
487
#endif
488
489
490
491
492
493
494
495
    }

    /* Add this new entry to the open_files structure if we have
     * multibuffer support, or to the main filestruct if we don't. */
    if (rc != -1 && new_buffer)
	load_file();
}

496
497
498
499
500
501
502
void do_insertfile(
#ifndef NANO_SMALL
	bool execute
#else
	void
#endif
	)
503
504
505
{
    int i;
    const char *msg;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
506
    char *ans = mallocstrcpy(NULL, "");
507
	/* The last answer the user typed on the statusbar. */
508
    filestruct *edittop_save = edittop;
509
    int current_y_save = current_y;
510
511
    bool at_edittop = FALSE;
	/* Whether we're at the top of the edit window. */
512

513
#ifndef DISABLE_WRAPPING
514
    wrap_reset();
515
#endif
516

517
    while (TRUE) {
518
#ifndef NANO_SMALL
519
	if (execute) {
520
#ifdef ENABLE_MULTIBUFFER
521
522
523
	    if (ISSET(MULTIBUFFER))
		msg = N_("Command to execute in new buffer [from %s] ");
	    else
524
#endif
525
526
		msg = N_("Command to execute [from %s] ");
	} else {
527
528
#endif
#ifdef ENABLE_MULTIBUFFER
529
530
531
	    if (ISSET(MULTIBUFFER)) {
		msg = N_("File to insert into new buffer [from %s] ");
	    } else
532
#endif
533
		msg = N_("File to insert [from %s] ");
534
535
#ifndef NANO_SMALL
	}
536
#endif
537

538
	i = statusq(TRUE,
539
#ifndef NANO_SMALL
540
		execute ? extcmd_list :
541
542
#endif
		insertfile_list, ans,
Chris Allegretta's avatar
Chris Allegretta committed
543
#ifndef NANO_SMALL
544
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
545
#endif
546
547
548
549
		_(msg),
#ifndef DISABLE_OPERATINGDIR
		operating_dir != NULL && strcmp(operating_dir, ".") != 0 ?
		operating_dir :
550
#endif
551
		"./");
552

553
554
	/* If we're in multibuffer mode and the filename or command is
	 * blank, open a new buffer instead of canceling. */
555
	if (i == -1 || (i == -2
556
#ifdef ENABLE_MULTIBUFFER
557
		&& !ISSET(MULTIBUFFER)
558
#endif
559
		)) {
560
561
562
	    statusbar(_("Cancelled"));
	    break;
	} else {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
563
	    size_t pww_save = placewewant;
564

565
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
566

567
#if !defined(NANO_SMALL) && defined(ENABLE_MULTIBUFFER)
568
569
570
571
572
573
	    if (i == TOGGLE_MULTIBUFFER_KEY) {
		/* Don't allow toggling if we're in view mode. */
		if (!ISSET(VIEW_MODE))
		    TOGGLE(MULTIBUFFER);
		continue;
	    }
574
#endif
575

576
#ifndef DISABLE_BROWSER
577
578
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
579

580
581
		if (tmp == NULL)
		    continue;
582

583
584
		free(answer);
		answer = tmp;
585
586
587
588

		/* We have a file now.  Get out of the statusbar prompt
		 * cleanly. */
		statusq_abort();
589
	    }
Chris Allegretta's avatar
Chris Allegretta committed
590
591
#endif

592
#ifndef NANO_SMALL
593
594
595
596
	    if (i == NANO_TOOTHERINSERT_KEY) {
		execute = !execute;
		continue;
	    }
597
#endif
598

599
600
	    /* If we don't have a file yet, go back to the statusbar
	     * prompt. */
601
602
603
604
605
	    if (i != 0
#ifdef ENABLE_MULTIBUFFER
		&& (i != -2 || !ISSET(MULTIBUFFER))
#endif
		)
606
607
		continue;

608
#ifdef ENABLE_MULTIBUFFER
609
	    if (!ISSET(MULTIBUFFER)) {
610
#endif
611
612
		/* If we're not inserting into a new buffer, partition
		 * the filestruct so that it contains no text and hence
613
614
615
		 * looks like a new buffer, and keep track of whether
		 * the top of the partition is the top of the edit
		 * window. */
616
		filepart = partition_filestruct(current, current_x,
617
			current, current_x);
618
		at_edittop = (fileage == edittop);
619
#ifdef ENABLE_MULTIBUFFER
620
	    }
621
622
#endif

623
624
625
626
627
628
#ifndef NANO_SMALL
	    if (execute)
		execute_command(answer);
	    else {
#endif
		answer = mallocstrassn(answer, real_dir_from_tilde(answer));
629
		load_buffer(answer);
630
631
632
#ifndef NANO_SMALL
	    }
#endif
633
634

#ifdef ENABLE_MULTIBUFFER
635
	    if (!ISSET(MULTIBUFFER))
636
#endif
637
638
	    {
		filestruct *top_save = fileage;
639

640
641
642
643
644
645
646
647
648
		/* If we didn't insert into a new buffer, and we were at
		 * the top of the edit window before, set the saved
		 * value of edittop to the new top of the edit window,
		 * and update the current y-coordinate to account for
		 * the number of lines inserted. */
		if (at_edittop)
		    edittop_save = fileage;
		current_y += current_y_save;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
649
		/* If we didn't insert into a new buffer, unpartition
650
651
652
653
		 * the filestruct so that it contains all the text
		 * again.  Note that we've replaced the non-text
		 * originally in the partition with the text in the
		 * inserted file/executed command output. */
654
		unpartition_filestruct(&filepart);
655

656
657
658
		/* Renumber starting with the beginning line of the old
		 * partition. */
		renumber(top_save);
659

660
661
		/* Set edittop back to what it was before. */
		edittop = edittop_save;
662
	    }
663

664
#ifdef ENABLE_MULTIBUFFER
665
666
667
	    if (ISSET(MULTIBUFFER)) {
		/* Update the titlebar. */
		titlebar(NULL);
668

669
670
671
		/* Reinitialize the shortcut list. */
		shortcut_init(FALSE);
	    } else {
672
#endif
673
674
		/* Mark the file as modified. */
		set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
675

676
		/* Restore the old place we want. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
677
		placewewant = pww_save;
678
#ifdef ENABLE_MULTIBUFFER
679
	    }
Chris Allegretta's avatar
Chris Allegretta committed
680
681
#endif

682
683
684
685
686
	    /* Refresh the screen. */
	    edit_refresh();

	    break;
	}
687
    }
Chris Allegretta's avatar
Chris Allegretta committed
688

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
689
    free(ans);
Chris Allegretta's avatar
Chris Allegretta committed
690
691
}

692
void do_insertfile_void(void)
693
{
694
#ifdef ENABLE_MULTIBUFFER
695
696
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
	statusbar(_("Key illegal in non-multibuffer mode"));
697
    else
698
#endif
699
700
701
702
703
	do_insertfile(
#ifndef NANO_SMALL
		FALSE
#endif
		);
704
705
706
707

    display_main_list();
}

708
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
709
/* Create a new openfilestruct node. */
710
openfilestruct *make_new_opennode(void)
Chris Allegretta's avatar
Chris Allegretta committed
711
{
712
713
    openfilestruct *newnode =
	(openfilestruct *)nmalloc(sizeof(openfilestruct));
Chris Allegretta's avatar
Chris Allegretta committed
714
715
716
717
718
719
    newnode->filename = NULL;
    return newnode;
}

/* Splice a node into an existing openfilestruct. */
void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
720
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
721
{
722
    assert(newnode != NULL && begin != NULL);
723

Chris Allegretta's avatar
Chris Allegretta committed
724
725
726
727
728
729
730
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

731
732
/* Unlink a node from the rest of the openfilestruct, and delete it. */
void unlink_opennode(openfilestruct *fileptr)
Chris Allegretta's avatar
Chris Allegretta committed
733
{
734
    assert(fileptr != NULL && fileptr->prev != NULL && fileptr->next != NULL && fileptr != fileptr->prev && fileptr != fileptr->next);
735

736
737
738
    fileptr->prev->next = fileptr->next;
    fileptr->next->prev = fileptr->prev;
    delete_opennode(fileptr);
Chris Allegretta's avatar
Chris Allegretta committed
739
740
741
742
743
}

/* Delete a node from the openfilestruct. */
void delete_opennode(openfilestruct *fileptr)
{
744
    assert(fileptr != NULL && fileptr->filename != NULL && fileptr->fileage != NULL);
745

746
747
748
    free(fileptr->filename);
    free_filestruct(fileptr->fileage);
    free(fileptr);
Chris Allegretta's avatar
Chris Allegretta committed
749
750
}

751
752
753
#ifdef DEBUG
/* Deallocate all memory associated with this and later files, including
 * the lines of text. */
Chris Allegretta's avatar
Chris Allegretta committed
754
755
void free_openfilestruct(openfilestruct *src)
{
756
    assert(src != NULL);
757

758
759
760
    while (src != src->next) {
	src = src->next;
	delete_opennode(src->prev);
Chris Allegretta's avatar
Chris Allegretta committed
761
    }
762
    delete_opennode(src);
Chris Allegretta's avatar
Chris Allegretta committed
763
}
764
#endif
Chris Allegretta's avatar
Chris Allegretta committed
765

766
/* Add/update an entry to the open_files openfilestruct.  If update is
767
 * FALSE, a new entry is created; otherwise, the current entry is
768
 * updated. */
769
void add_open_file(bool update)
770
{
771
    if (open_files == NULL && update)
772
	return;
773

774
775
776
777
778
779
780
781
782
    /* If there are no entries in open_files, make the first one. */
    if (open_files == NULL) {
	open_files = make_new_opennode();
	splice_opennode(open_files, open_files, open_files);
    /* Otherwise, if we're not updating, make a new entry for
     * open_files and splice it in after the current entry. */
    } else if (!update) {
	splice_opennode(open_files, make_new_opennode(),
		open_files->next);
783
784
785
	open_files = open_files->next;
    }

786
    /* Save the current filename. */
787
    open_files->filename = mallocstrcpy(open_files->filename, filename);
788

789
#ifndef NANO_SMALL
790
    /* Save the current file's stat. */
791
792
793
    open_files->originalfilestat = originalfilestat;
#endif

794
795
796
797
798
799
800
801
802
    /* Save the current file buffer. */
    open_files->fileage = fileage;
    open_files->filebot = filebot;

    /* Save the current top of the edit window. */
    open_files->edittop = edittop;

    /* Save the current line. */
    open_files->current = current;
803

804
805
    /* Save the current cursor position. */
    open_files->current_x = current_x;
806

807
808
    /* Save the current place we want. */
    open_files->placewewant = placewewant;
809

810
811
    /* Save the current total number of lines. */
    open_files->totlines = totlines;
812

813
814
    /* Save the current total size. */
    open_files->totsize = totsize;
815

816
817
    /* Start with no flags saved. */
    open_files->flags = 0;
818

819
820
821
    /* Save the current modification status. */
    if (ISSET(MODIFIED))
	open_files->flags |= MODIFIED;
822

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
823
#ifndef NANO_SMALL
824
825
826
827
828
    /* Save the current marking status and mark, if applicable. */
    if (ISSET(MARK_ISSET)) {
	open_files->flags |= MARK_ISSET;
	open_files->mark_beginbuf = mark_beginbuf;
	open_files->mark_beginx = mark_beginx;
Chris Allegretta's avatar
Chris Allegretta committed
829
830
    }

831
832
833
    /* Save the current file format. */
    open_files->fmt = fmt;
#endif
834
835

#ifdef DEBUG
836
    fprintf(stderr, "filename is %s\n", open_files->filename);
837
838
839
#endif
}

840
/* Read the current entry in the open_files structure and set up the
841
 * currently open file buffer using that entry's information. */
842
void load_open_file(void)
843
{
844
    assert(open_files != NULL);
845

846
    /* Restore the current filename. */
847
    filename = mallocstrcpy(filename, open_files->filename);
848

849
#ifndef NANO_SMALL
850
    /* Restore the current file's stat. */
851
852
    originalfilestat = open_files->originalfilestat;
#endif
853
854

    /* Restore the current file buffer. */
855
    fileage = open_files->fileage;
Chris Allegretta's avatar
Chris Allegretta committed
856
    filebot = open_files->filebot;
857

858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
    /* Restore the current top of the edit window. */
    edittop = open_files->edittop;

    /* Restore the current line. */
    current = open_files->current;

    /* Restore the current cursor position. */
    current_x = open_files->current_x;

    /* Restore the current place we want. */
    placewewant = open_files->placewewant;

    /* Restore the current total number of lines. */
    totlines = open_files->totlines;

    /* Restore the current total size. */
    totsize = open_files->totsize;

    /* Restore the current modification status. */
    if (open_files->flags & MODIFIED)
Chris Allegretta's avatar
Chris Allegretta committed
878
879
880
881
882
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

#ifndef NANO_SMALL
883
884
885
886
    /* Restore the current marking status and mark, if applicable. */
    if (open_files->flags & MARK_ISSET) {
	mark_beginbuf = open_files->mark_beginbuf;
	mark_beginx = open_files->mark_beginx;
Chris Allegretta's avatar
Chris Allegretta committed
887
888
889
	SET(MARK_ISSET);
    } else
	UNSET(MARK_ISSET);
890

891
892
    /* Restore the current file format. */
    fmt = open_files->fmt;
Chris Allegretta's avatar
Chris Allegretta committed
893
#endif
894

Chris Allegretta's avatar
Chris Allegretta committed
895
896
897
#ifdef ENABLE_COLOR
    update_color();
#endif
898
    edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
899

900
    /* Update the titlebar. */
901
902
903
    titlebar(NULL);
}

904
/* Open either the next or previous file buffer. */
905
void open_prevnext_file(bool next)
906
{
907
    add_open_file(TRUE);
908

909
    assert(open_files != NULL);
910

911
912
    /* If only one file buffer is open, indicate it on the statusbar and
     * get out. */
913
    if (open_files == open_files->next) {
914
	statusbar(_("No more open file buffers"));
915
	return;
916
917
    }

918
919
920
    /* Switch to the next or previous file, depending on the value of
     * next. */
    open_files = next ? open_files->next : open_files->prev;
921
922

#ifdef DEBUG
923
    fprintf(stderr, "filename is %s\n", open_files->filename);
924
925
#endif

926
    /* Load the file we switched to. */
927
928
    load_open_file();

929
    /* And indicate the switch on the statusbar. */
930
    statusbar(_("Switched to %s"),
931
      ((open_files->filename[0] == '\0') ? _("New Buffer") :
932
	open_files->filename));
933

934
935
936
937
938
#ifdef DEBUG
    dump_buffer(current);
#endif
}

939
940
/* Open the previous entry in the open_files structure.  This function
 * is used by the shortcut list. */
941
void open_prevfile_void(void)
942
{
943
    open_prevnext_file(FALSE);
944
945
}

946
947
/* Open the next entry in the open_files structure.  This function is
 * used by the shortcut list. */
948
void open_nextfile_void(void)
949
{
950
    open_prevnext_file(TRUE);
951
952
}

953
954
/* Delete an entry from the open_files filestruct.  After deletion of an
 * entry, the next entry is opened.  Return TRUE on success or FALSE if
955
 * there are no more open file buffers. */
956
bool close_open_file(void)
957
{
958
    assert(open_files != NULL);
959

960
961
    /* If only one file is open, get out. */
    if (open_files == open_files->next)
962
	return FALSE;
963

964
965
    /* Open the next file. */
    open_nextfile_void();
Chris Allegretta's avatar
Chris Allegretta committed
966

967
968
    /* Close the file we had open before. */
    unlink_opennode(open_files->prev);
969

970
    /* Reinitialize the shortcut list. */
971
    shortcut_init(FALSE);
972
    display_main_list();
973

974
    return TRUE;
975
}
976
#endif /* ENABLE_MULTIBUFFER */
977

978
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
979
/* When passed "[relative path]" or "[relative path][filename]" in
980
 * origpath, return "[full path]" or "[full path][filename]" on success,
981
982
983
984
 * or NULL on error.  Do this if the file doesn't exist but the relative
 * path does, since the file could exist in memory but not yet on disk).
 * Don't do this if the relative path doesn't exist, since we won't be
 * able to go there. */
985
char *get_full_path(const char *origpath)
986
{
987
    char *d_here, *d_there = NULL;
988

989
990
    if (origpath == NULL)
    	return NULL;
991

992
993
994
    /* Get the current directory. */
    d_here = charalloc(PATH_MAX + 1);
    d_here = getcwd(d_here, PATH_MAX + 1);
995

996
    if (d_here != NULL) {
997
998
999
1000
	const char *last_slash;
	char *d_there_file = NULL;
	bool path_only;
	struct stat fileinfo;
1001

1002
1003
	align(&d_here);

1004
1005
	/* If the current directory isn't "/", tack a slash onto the end
	 * of it. */
1006
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1007
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1008
1009
	    strcat(d_here, "/");
	}
1010

1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
	d_there = real_dir_from_tilde(origpath);

	assert(d_there != NULL);

	/* Stat d_there.  If stat() fails, assume that d_there refers to
	 * a new file that hasn't been saved to disk yet.  Set path_only
	 * to TRUE if d_there refers to a directory, and FALSE if
	 * d_there refers to a file. */
	path_only = !stat(d_there, &fileinfo) &&
		S_ISDIR(fileinfo.st_mode);

	/* If path_only is TRUE, make sure d_there ends in a slash. */
1023
	if (path_only) {
1024
1025
1026
1027
	    size_t d_there_len = strlen(d_there);

	    if (d_there[d_there_len - 1] != '/') {
		d_there = charealloc(d_there, d_there_len + 2);
1028
1029
1030
1031
		strcat(d_there, "/");
	    }
	}

1032
	/* Search for the last slash in d_there. */
1033
1034
	last_slash = strrchr(d_there, '/');

1035
1036
1037
1038
	/* If we didn't find one, then make sure the answer is in the
	 * format "d_here/d_there". */
	if (last_slash == NULL) {
	    assert(!path_only);
1039

1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
	    d_there_file = d_there;
	    d_there = d_here;
	} else {
	    /* If path_only is FALSE, then save the filename portion of
	     * the answer, everything after the last slash, in
	     * d_there_file. */
	    if (!path_only)
		d_there_file = mallocstrcpy(NULL, last_slash + 1);

	    /* And remove the filename portion of the answer from
	     * d_there. */
	    null_at(&d_there, last_slash - d_there + 1);

	    /* Go to the path specified in d_there. */
	    if (chdir(d_there) == -1) {
1055
		free(d_there);
1056
1057
1058
1059
		d_there = NULL;
	    } else {
		/* Get the full path and save it in d_there. */
		free(d_there);
1060

1061
1062
1063
		d_there = charalloc(PATH_MAX + 1);
		d_there = getcwd(d_there, PATH_MAX + 1);

1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
		if (d_there != NULL) {
		    align(&d_there);

		    if (strcmp(d_there, "/") != 0) {
			/* Make sure d_there ends in a slash. */
			d_there = charealloc(d_there,
				strlen(d_there) + 2);
			strcat(d_there, "/");
		    }
		} else
1074
1075
1076
1077
1078
1079
1080
1081
		    /* If we couldn't get the full path, set path_only
		     * to TRUE so that we clean up correctly, free all
		     * allocated memory, and return NULL. */
		    path_only = TRUE;

		/* Finally, go back to the path specified in d_here,
		 * where we were before. */
		chdir(d_here);
1082
1083
	    }

1084
1085
	    /* Free d_here, since we're done using it. */
	    free(d_here);
1086
	}
1087

1088
1089
1090
1091
1092
	/* At this point, if path_only is FALSE and d_there exists,
	 * d_there contains the path portion of the answer and
	 * d_there_file contains the filename portion of the answer.  If
	 * this is the case, tack d_there_file onto the end of
	 * d_there, so that d_there contains the complete answer. */
1093
	if (!path_only && d_there != NULL) {
1094
1095
1096
1097
1098
1099
	    d_there = charealloc(d_there, strlen(d_there) +
		strlen(d_there_file) + 1);
	    strcat(d_there, d_there_file);
 	}

	/* Free d_there_file, since we're done using it. */
1100
1101
1102
	free(d_there_file);
    }

1103
    return d_there;
1104
}
1105
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1106
1107

#ifndef DISABLE_SPELLER
1108
1109
1110
/* Return the full version of path, as returned by get_full_path().  On
 * error, if path doesn't reference a directory, or if the directory
 * isn't writable, return NULL. */
1111
char *check_writable_directory(const char *path)
1112
{
1113
1114
    char *full_path = get_full_path(path);

1115
    /* If get_full_path() fails, return NULL. */
1116
    if (full_path == NULL)
1117
	return NULL;
1118

1119
1120
1121
1122
    /* If we can't write to path or path isn't a directory, return
     * NULL. */
    if (access(full_path, W_OK) != 0 ||
		full_path[strlen(full_path) - 1] != '/') {
1123
	free(full_path);
1124
	return NULL;
1125
    }
1126
1127
1128
1129
1130

    /* otherwise, return the full path */
    return full_path;
}

1131
1132
1133
1134
/* This function acts like a call to tempnam(NULL, "nano.").  The
 * difference is that the number of calls is not limited by TMP_MAX.
 * Instead we use mkstemp(). */
char *safe_tempnam(void)
1135
{
1136
1137
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1138
    int filedesc;
1139

1140
1141
1142
    /* 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, leave full_tempdir set to NULL. */
1143
    TMPDIR_env = getenv("TMPDIR");
1144
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1145
	full_tempdir = check_writable_directory(TMPDIR_env);
1146

1147
1148
    /* If $TMPDIR is unset, empty, or not a writable directory, and
     * full_tempdir is NULL, try P_tmpdir instead. */
1149
    if (full_tempdir == NULL)
1150
	full_tempdir = check_writable_directory(P_tmpdir);
1151

1152
1153
1154
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1155

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1156
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1157
    strcat(full_tempdir, "nano.XXXXXX");
1158
1159
    filedesc = mkstemp(full_tempdir);

1160
1161
    /* If mkstemp() succeeded, close the resulting file, delete it
     * (since it'll be 0 bytes long), and return the filename. */
1162
    if (filedesc != -1) {
1163
	close(filedesc);
1164
1165
	unlink(full_tempdir);
	return full_tempdir;
1166
    }
1167
1168

    free(full_tempdir);
1169

1170
    return NULL;
1171
1172
}
#endif /* !DISABLE_SPELLER */
1173
1174

#ifndef DISABLE_OPERATINGDIR
1175
1176
1177
1178
1179
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1180
    if (operating_dir == NULL)
1181
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1182

1183
1184
1185
    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
1186
     * inaccessible, unset operating_dir. */
1187
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1188
1189
1190
1191
1192
1193
1194
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1195
1196
1197
1198
1199
/* Check to see if we're inside the operating directory.  Return FALSE
 * if we are, or TRUE otherwise.  If allow_tabcomp is TRUE, allow
 * incomplete names that would be matches for the operating directory,
 * so that tab completion will work. */
bool check_operating_dir(const char *currpath, bool allow_tabcomp)
1200
{
1201
1202
1203
1204
    /* 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. */
1205

1206
    char *fullpath;
1207
    bool retval = FALSE;
1208
    const char *whereami1, *whereami2 = NULL;
1209

1210
    /* If no operating directory is set, don't bother doing anything. */
1211
    if (operating_dir == NULL)
1212
	return FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1213

1214
    assert(full_operating_dir != NULL);
1215
1216

    fullpath = get_full_path(currpath);
1217
1218

    /* fullpath == NULL means some directory in the path doesn't exist
1219
     * or is unreadable.  If allow_tabcomp is FALSE, then currpath is
1220
1221
     * what the user typed somewhere.  We don't want to report a
     * non-existent directory as being outside the operating directory,
1222
     * so we return FALSE.  If allow_tabcomp is TRUE, then currpath
1223
1224
     * exists, but is not executable.  So we say it isn't in the
     * operating directory. */
1225
    if (fullpath == NULL)
1226
	return allow_tabcomp;
1227
1228
1229
1230
1231

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

1232
1233
1234
1235
1236
    /* 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. */
1237
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1238
1239
	retval = TRUE;
    free(fullpath);
1240
1241

    /* Otherwise, we're still inside it. */
1242
    return retval;
1243
}
1244
1245
#endif

1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
#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

1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
/* 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);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1280

1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
    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);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1292

1293
1294
1295
1296
    if (fclose(inn) == EOF)
	retval = -1;
    if (fclose(out) == EOF)
	retval = -2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1297

1298
1299
1300
    return retval;
}

1301
/* Write a file out.  If tmp is FALSE, we set the umask to disallow
1302
1303
1304
 * 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
1305
 *
1306
1307
 * 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.
1308
 *
1309
1310
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1311
 *
1312
1313
 * nonamechange means don't change the current filename.  It is ignored
 * if tmp is FALSE or if we're appending/prepending.
1314
1315
 *
 * Return -1 on error, 1 on success. */
1316
1317
int write_file(const char *name, bool tmp, int append, bool
	nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1318
{
1319
1320
    int retval = -1;
	/* Instead of returning in this function, you should always
1321
1322
1323
	 * merely set retval and then goto cleanup_and_exit. */
    size_t lineswritten = 0;
    const filestruct *fileptr = fileage;
1324
    int fd;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1325
	/* The file descriptor we use. */
1326
    mode_t original_umask = 0;
1327
	/* Our umask, from when nano started. */
1328
    bool realexists;
1329
	/* The result of stat().  TRUE if the file exists, FALSE
1330
	 * otherwise.  If name is a link that points nowhere, realexists
1331
	 * is FALSE. */
1332
1333
    struct stat st;
	/* The status fields filled in by stat(). */
1334
    bool anyexists;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1335
	/* The result of lstat().  Same as realexists unless name is a
1336
1337
1338
1339
	 * link. */
    struct stat lst;
	/* The status fields filled in by lstat(). */
    char *realname;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1340
	/* name after tilde expansion. */
1341
1342
1343
1344
    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
1345

1346
    assert(name != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1347

1348
    if (name[0] == '\0')
Chris Allegretta's avatar
Chris Allegretta committed
1349
	return -1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1350

1351
1352
    if (!tmp)
	titlebar(NULL);
1353

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

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

1365
    anyexists = (lstat(realname, &lst) != -1);
1366

1367
1368
1369
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
1370

1371
1372
1373
    /* 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)) {
1374
1375
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1376
1377
1378
	goto cleanup_and_exit;
    }

1379
    /* Save the state of file at the end of the symlink (if there is
1380
     * one). */
1381
    realexists = (stat(realname, &st) != -1);
1382

1383
1384
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1385
1386
1387
1388
     * 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. */
1389
    if (ISSET(BACKUP_FILE) && !tmp && realexists &&
1390
1391
1392
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1393
	FILE *backup_file;
1394
	char *backupname;
1395
	struct utimbuf filetime;
1396
	int copy_status;
1397

1398
	/* Save the original file's access and modification times. */
1399
1400
1401
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1402
	/* Open the original file to copy to the backup. */
1403
	f = fopen(realname, "rb");
1404
	if (f == NULL) {
1405
	    statusbar(_("Error reading %s: %s"), realname,
1406
		strerror(errno));
1407
	    goto cleanup_and_exit;
1408
1409
	}

1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
	/* 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);
	}
1442

1443
1444
1445
	/* Open the destination backup file.  Before we write to it, we
	 * set its permissions, so no unauthorized person can read it as
	 * we write. */
1446
	backup_file = fopen(backupname, "wb");
1447
1448
	if (backup_file == NULL ||
		chmod(backupname, originalfilestat.st_mode) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1449
1450
	    statusbar(_("Error writing %s: %s"), backupname,
		strerror(errno));
1451
	    free(backupname);
1452
1453
1454
1455
	    if (backup_file != NULL)
		fclose(backup_file);
	    fclose(f);
	    goto cleanup_and_exit;
1456
1457
1458
	}

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

1462
1463
	/* Copy the file. */
	copy_status = copy_file(f, backup_file);
1464

1465
	/* And set metadata. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1466
	if (copy_status != 0 || chown(backupname,
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1467
1468
1469
		originalfilestat.st_uid,
		originalfilestat.st_gid) == -1 || utime(backupname,
		&filetime) == -1) {
1470
1471
	    free(backupname);
	    if (copy_status == -1)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1472
1473
		statusbar(_("Error reading %s: %s"), realname,
			strerror(errno));
1474
1475
1476
1477
1478
	    else
		statusbar(_("Error writing %s: %s"), backupname,
			strerror(errno));
	    goto cleanup_and_exit;
	}
1479
1480
	free(backupname);
    }
1481
#endif /* !NANO_SMALL */
1482

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1483
1484
    /* If NOFOLLOW_SYMLINKS is set and the file is a link, we aren't
     * doing prepend or append.  So we delete the link first, and just
1485
1486
1487
1488
     * overwrite. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode) &&
	unlink(realname) == -1) {
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1489
	goto cleanup_and_exit;
1490
    }
1491

1492
1493
    original_umask = umask(0);
    umask(original_umask);
1494

1495
    /* If we create a temp file, we don't let anyone else access it.  We
1496
     * create a temp file if tmp is TRUE or if we're prepending. */
1497
1498
1499
    if (tmp || append == 2)
	umask(S_IRWXG | S_IRWXO);

1500
    /* If we're prepending, copy the file to a temp file. */
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
    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) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1516
1517
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1518
	    unlink(tempname);
1519
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1520
	}
1521

1522
1523
1524
1525
1526
1527
1528
	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) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1529
1530
	    statusbar(_("Error reading %s: %s"), realname,
		strerror(errno));
1531
1532
1533
1534
1535
1536
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

	if (copy_file(f_source, f) != 0) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1537
1538
	    statusbar(_("Error writing %s: %s"), tempname,
		strerror(errno));
1539
	    unlink(tempname);
1540
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1541
1542
1543
	}
    }

1544
1545
    /* Now open the file in place.  Use O_EXCL if tmp is TRUE.  This is
     * now copied from joe, because wiggy says so *shrug*. */
1546
    fd = open(realname, O_WRONLY | O_CREAT |
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1547
	((append == 1) ? O_APPEND : (tmp ? O_EXCL : O_TRUNC)),
1548
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
1549

1550
    /* Set the umask back to the user's original value. */
1551
1552
1553
1554
1555
    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));
1556
1557
1558
	/* tempname has been set only if we're prepending. */
	if (tempname != NULL)
	    unlink(tempname);
1559
1560
	goto cleanup_and_exit;
    }
1561

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1562
    f = fdopen(fd, (append == 1) ? "ab" : "wb");
1563
    if (f == NULL) {
1564
1565
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1566
	goto cleanup_and_exit;
1567
1568
    }

1569
    /* There might not be a magicline.  There won't be when writing out
1570
1571
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1572

1573
1574
1575
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1576

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

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

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

1585
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1586
1587
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1588
	    fclose(f);
1589
	    goto cleanup_and_exit;
1590
	}
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1591

1592
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1593
	if (fmt == DOS_FILE || fmt == MAC_FILE) {
1594
	    if (putc('\r', f) == EOF) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1595
1596
		statusbar(_("Error writing %s: %s"), realname,
			strerror(errno));
1597
1598
1599
		fclose(f);
		goto cleanup_and_exit;
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1600
	}
Chris Allegretta's avatar
Chris Allegretta committed
1601

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1602
	if (fmt != MAC_FILE) {
1603
#endif
1604
	    if (putc('\n', f) == EOF) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1605
1606
		statusbar(_("Error writing %s: %s"), realname,
			strerror(errno));
1607
1608
1609
		fclose(f);
		goto cleanup_and_exit;
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1610
1611
1612
#ifndef NANO_SMALL
	}
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1613
1614
1615
1616
1617

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

1618
    /* If we're prepending, open the temp file, and append it to f. */
1619
    if (append == 2) {
1620
1621
1622
1623
1624
1625
1626
1627
	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);
1628
	}
1629
	if (f_source == NULL) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1630
1631
	    statusbar(_("Error reading %s: %s"), tempname,
		strerror(errno));
1632
	    fclose(f);
1633
	    goto cleanup_and_exit;
1634
1635
	}

1636
	if (copy_file(f_source, f) == -1 || unlink(tempname) == -1) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1637
1638
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1639
	    goto cleanup_and_exit;
1640
	}
1641
1642
1643
1644
    } 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
1645
    }
1646

1647
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1648
	if (!nonamechange) {
1649
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1650
1651
#ifdef ENABLE_COLOR
	    update_color();
1652
	    if (!ISSET(NO_COLOR_SYNTAX))
1653
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1654
1655
#endif
	}
1656

1657
1658
1659
1660
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1661
1662
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1663
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1664
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1665
    }
1666
1667
1668
1669
1670

    retval = 1;

  cleanup_and_exit:
    free(realname);
1671
    free(tempname);
1672
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1673
1674
}

1675
1676
1677
#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
1678
1679
1680
1681
 * write_file() with the values of name, temp, and append, and with
 * nonamechange set to TRUE so that we don't change the current
 * filename.  Finally, set fileage and filebot back to their old values
 * and return. */
1682
int write_marked(const char *name, bool tmp, int append)
1683
1684
{
    int retval = -1;
1685
    bool old_modified = ISSET(MODIFIED);
1686
	/* write_file() unsets the MODIFIED flag. */
1687
1688
1689
1690
1691
1692
1693
1694
    bool added_magicline;
	/* Whether we added a magicline after filebot. */
    filestruct *top, *bot;
    size_t top_x, bot_x;

    /* Partition the filestruct so that it contains only the marked
     * text. */
    mark_order((const filestruct **)&top, &top_x,
1695
	(const filestruct **)&bot, &bot_x, NULL);
1696
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1697
1698

    /* If the line at filebot is blank, treat it as the magicline and
1699
1700
1701
1702
1703
     * hence the end of the file.  Otherwise, add a magicline and treat
     * it as the end of the file. */
    added_magicline = (filebot->data[0] != '\0');
    if (added_magicline)
	new_magicline();
1704

1705
    retval = write_file(name, tmp, append, TRUE);
1706

1707
1708
1709
1710
1711
1712
    /* If we added a magicline, remove it now. */
    if (added_magicline)
	remove_magicline();

    /* Unpartition the filestruct so that it contains all the text
     * again. */
1713
    unpartition_filestruct(&filepart);
1714

1715
    if (old_modified)
1716
1717
1718
1719
1720
1721
	set_modified();

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

1722
int do_writeout(bool exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1723
{
1724
    int i;
1725
1726
1727
    int retval = 0, append = 0;
    char *ans;
	/* The last answer the user typed on the statusbar. */
1728
#ifdef NANO_EXTRA
1729
    static bool did_cred = FALSE;
1730
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1731

1732
    currshortcut = writefile_list;
1733

1734
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1735
1736
1737
1738
1739
	retval = write_file(filename, FALSE, 0, FALSE);

	/* Write succeeded. */
	if (retval == 1)
	    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1740
1741
    }

1742
#ifndef NANO_SMALL
1743
    if (ISSET(MARK_ISSET) && !exiting)
1744
	ans = mallocstrcpy(NULL, "");
1745
1746
    else
#endif
1747
	ans = mallocstrcpy(NULL, filename);
1748
1749
1750
1751

    while (TRUE) {
	const char *msg;
#ifndef NANO_SMALL
1752
1753
	const char *formatstr, *backupstr;

1754
	if (fmt == DOS_FILE)
1755
	   formatstr = N_(" [DOS Format]");
1756
	else if (fmt == MAC_FILE)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1757
	   formatstr = N_(" [Mac Format]");
1758
1759
1760
	else
	   formatstr = "";

1761
	if (ISSET(BACKUP_FILE))
1762
	   backupstr = N_(" [Backup]");
1763
1764
1765
	else
	   backupstr = "";

1766
	/* Be nice to the translation folks. */
1767
	if (ISSET(MARK_ISSET) && !exiting) {
1768
	    if (append == 2)
1769
		msg = N_("Prepend Selection to File");
Chris Allegretta's avatar
Chris Allegretta committed
1770
	    else if (append == 1)
1771
		msg = N_("Append Selection to File");
1772
	    else
1773
		msg = N_("Write Selection to File");
1774
1775
	} else
#endif /* !NANO_SMALL */
1776
	if (append == 2)
1777
	    msg = N_("File Name to Prepend to");
Chris Allegretta's avatar
Chris Allegretta committed
1778
	else if (append == 1)
1779
	    msg = N_("File Name to Append to");
1780
	else
1781
	    msg = N_("File Name to Write");
1782

1783
1784
1785
	/* If we're using restricted mode, the filename isn't blank,
	 * and we're at the "Write File" prompt, disable tab
	 * completion. */
1786
	i = statusq(!ISSET(RESTRICTED) || filename[0] == '\0',
1787
		writefile_list, ans,
1788
#ifndef NANO_SMALL
1789
		NULL, "%s%s%s", _(msg), formatstr, backupstr
1790
#else
1791
		"%s", _(msg)
1792
1793
1794
#endif
		);

1795
	if (i < 0) {
1796
	    statusbar(_("Cancelled"));
1797
1798
1799
1800
	    retval = -1;
	    break;
	} else {
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
1801

1802
#ifndef DISABLE_BROWSER
1803
1804
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
1805

1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
		currshortcut = writefile_list;

		if (tmp == NULL)
		    continue;
		free(answer);
		answer = tmp;

		/* We have a file now.  Get out of the statusbar prompt
		 * cleanly. */
		statusq_abort();
	    } else
1817
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1818
#ifndef NANO_SMALL
1819
	    if (i == TOGGLE_DOS_KEY) {
1820
		fmt = (fmt == DOS_FILE) ? NIX_FILE : DOS_FILE;
1821
1822
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1823
		fmt = (fmt == MAC_FILE) ? NIX_FILE : MAC_FILE;
1824
1825
1826
1827
1828
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1829
#endif /* !NANO_SMALL */
1830
1831
1832
1833
1834
1835
1836
	    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
1837

Chris Allegretta's avatar
Chris Allegretta committed
1838
#ifdef DEBUG
1839
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1840
#endif
1841
1842

#ifdef NANO_EXTRA
1843
1844
1845
1846
1847
1848
1849
	    if (exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0 && !did_cred) {
		do_credits();
		did_cred = TRUE;
		retval = -1;
		break;
	    }
1850
#endif
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
	    if (append == 0 && strcmp(answer, filename) != 0) {
		struct stat st;

		if (!stat(answer, &st)) {
		    i = do_yesno(FALSE, _("File exists, OVERWRITE ? "));
		    if (i == 0 || i == -1)
			continue;
		/* If we're using restricted mode, we aren't allowed to
		 * change the name of a file once it has one because
		 * that would allow reading from or writing to files not
		 * specified on the command line.  In this case, don't
		 * bother showing the "Different Name" prompt. */
		} else if (!ISSET(RESTRICTED) && filename[0] != '\0'
1864
#ifndef NANO_SMALL
1865
			&& (exiting || !ISSET(MARK_ISSET))
1866
#endif
1867
			) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1868
1869
		    i = do_yesno(FALSE,
			_("Save file under DIFFERENT NAME ? "));
1870
1871
1872
		    if (i == 0 || i == -1)
			continue;
		}
1873
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1874

1875
#ifndef NANO_SMALL
1876
1877
1878
1879
1880
1881
1882
	    /* Here's where we allow the selected text to be written to
	     * a separate file.  If we're using restricted mode, this is
	     * disabled since it allows reading from or writing to files
	     * not specified on the command line. */
	    if (!ISSET(RESTRICTED) && !exiting && ISSET(MARK_ISSET))
		retval = write_marked(answer, FALSE, append);
	    else
1883
#endif /* !NANO_SMALL */
1884
		retval = write_file(answer, FALSE, append, FALSE);
1885

1886
#ifdef ENABLE_MULTIBUFFER
1887
1888
1889
1890
	    /* If we're not about to exit, update the current entry in
	     * the open_files structure. */
	    if (!exiting)
		add_open_file(TRUE);
1891
#endif
1892
1893
1894

	    break;
	}
1895
    } /* while (TRUE) */
1896
1897

    free(ans);
1898

1899
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1900
1901
}

1902
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1903
{
1904
    do_writeout(FALSE);
1905
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1906
}
Chris Allegretta's avatar
Chris Allegretta committed
1907

1908
1909
/* Return a malloc()ed string containing the actual directory, used to
 * convert ~user/ and ~/ notation. */
1910
char *real_dir_from_tilde(const char *buf)
1911
{
Chris Allegretta's avatar
Chris Allegretta committed
1912
    char *dirtmp = NULL;
1913

1914
1915
1916
    if (buf == NULL)
    	return NULL;

1917
    if (buf[0] == '~') {
1918
	size_t i;
1919
	const char *tilde_dir;
1920

1921
	/* Figure out how much of the str we need to compare. */
1922
1923
1924
	for (i = 1; buf[i] != '/' && buf[i] != '\0'; i++)
	    ;

1925
1926
1927
1928
1929
1930
1931
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
	    tilde_dir = homedir;
	} else {
	    const struct passwd *userdata;

1932
1933
1934
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1935
		strncmp(userdata->pw_name, buf + 1, i - 1) != 0);
1936
1937
	    endpwent();
	    tilde_dir = userdata->pw_dir;
Chris Allegretta's avatar
Chris Allegretta committed
1938
	}
1939

1940
1941
1942
	if (tilde_dir != NULL) {
	    dirtmp = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	    sprintf(dirtmp, "%s%s", tilde_dir, buf + i);
1943
	}
1944
    }
1945

1946
1947
    /* Set a default value for dirtmp, in case the user's home directory
     * isn't found. */
1948
    if (dirtmp == NULL)
1949
	dirtmp = mallocstrcpy(NULL, buf);
1950

1951
    return dirtmp;
1952
1953
}

1954
#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
1955
1956
/* Our sort routine for file listings.  Sort alphabetically and
 * case-insensitively, and sort directories before filenames. */
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
int diralphasort(const void *va, const void *vb)
{
    struct stat fileinfo;
    const char *a = *(const char *const *)va;
    const char *b = *(const char *const *)vb;
    bool aisdir = stat(a, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
    bool bisdir = stat(b, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);

    if (aisdir && !bisdir)
	return -1;
    if (!aisdir && bisdir)
	return 1;

    return strcasecmp(a, b);
}
#endif

Chris Allegretta's avatar
Chris Allegretta committed
1974
#ifndef DISABLE_TABCOMP
1975
1976
/* Is the given file a directory? */
int is_dir(const char *buf)
1977
{
1978
    char *dirptr = real_dir_from_tilde(buf);
1979
1980
    struct stat fileinfo;

1981
1982
    int ret = (stat(dirptr, &fileinfo) != -1 &&
		S_ISDIR(fileinfo.st_mode));
1983

1984
    assert(buf != NULL && dirptr != buf);
1985

1986
    free(dirptr);
1987

1988
    return ret;
1989
}
Chris Allegretta's avatar
Chris Allegretta committed
1990

1991
/* These functions (username_tab_completion(), cwd_tab_completion(), and
Chris Allegretta's avatar
Chris Allegretta committed
1992
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1993
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
 *
 * 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.
2006
 * This code may safely be consumed by a BSD or GPL license. */
Chris Allegretta's avatar
Chris Allegretta committed
2007

2008
2009
2010
2011
/* We consider the first buflen characters of buf for ~username tab
 * completion. */
char **username_tab_completion(const char *buf, size_t *num_matches,
	size_t buflen)
Chris Allegretta's avatar
Chris Allegretta committed
2012
{
2013
2014
    char **matches = NULL;
    const struct passwd *userdata;
2015

2016
    assert(buf != NULL && num_matches != NULL && buflen > 0);
2017

2018
    *num_matches = 0;
2019

2020
    while ((userdata = getpwent()) != NULL) {
2021
2022
2023
	if (strncmp(userdata->pw_name, buf + 1, buflen - 1) == 0) {
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2024

2025
2026
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2027
2028
2029
	     * directory, in which case just go to the next match. */
	    if (check_operating_dir(userdata->pw_dir, TRUE))
		continue;
2030
2031
#endif

2032
2033
2034
2035
2036
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
	    matches[*num_matches] =
		charalloc(strlen(userdata->pw_name) + 2);
	    sprintf(matches[*num_matches], "~%s", userdata->pw_name);
2037
	    ++(*num_matches);
2038
	}
2039
2040
    }
    endpwent();
2041

2042
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2043
2044
2045
}

/* This was originally called exe_n_cwd_tab_completion, but we're not
2046
2047
2048
 * worried about executables, only filenames :> */
char **cwd_tab_completion(const char *buf, size_t *num_matches, size_t
	buflen)
Chris Allegretta's avatar
Chris Allegretta committed
2049
{
2050
2051
2052
2053
2054
2055
2056
    char *dirname = mallocstrcpy(NULL, buf);
    char *filename;
#ifndef DISABLE_OPERATINGDIR
    size_t dirnamelen;
#endif
    size_t filenamelen;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2057
    DIR *dir;
2058
    const struct dirent *next;
Chris Allegretta's avatar
Chris Allegretta committed
2059

2060
    assert(dirname != NULL && num_matches != NULL && buflen >= 0);
2061

2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
    *num_matches = 0;
    null_at(&dirname, buflen);

    /* Okie, if there's a / in the buffer, strip out the directory
     * part. */
    filename = strrchr(dirname, '/');
    if (filename != NULL) {
	char *tmpdirname = filename + 1;

	filename = mallocstrcpy(NULL, tmpdirname);
	*tmpdirname = '\0';
	tmpdirname = dirname;
	dirname = real_dir_from_tilde(dirname);
	free(tmpdirname);
Chris Allegretta's avatar
Chris Allegretta committed
2076
    } else {
2077
2078
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2079
2080
    }

2081
    assert(dirname[strlen(dirname) - 1] == '/');
2082

Chris Allegretta's avatar
Chris Allegretta committed
2083
    dir = opendir(dirname);
2084

2085
    if (dir == NULL) {
2086
	/* Don't print an error, just shut up and return. */
Chris Allegretta's avatar
Chris Allegretta committed
2087
	beep();
2088
2089
2090
	free(filename);
	free(dirname);
	return NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2091
    }
2092
2093
2094
2095
2096
2097

#ifndef DISABLE_OPERATINGDIR
    dirnamelen = strlen(dirname);
#endif
    filenamelen = strlen(filename);

Chris Allegretta's avatar
Chris Allegretta committed
2098
2099
2100
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2101
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2102
#endif
2103
2104
2105
2106
2107
2108
	/* See if this matches. */
	if (strncmp(next->d_name, filename, filenamelen) == 0 &&
		(*filename == '.' || (strcmp(next->d_name, ".") != 0 &&
		strcmp(next->d_name, "..") != 0))) {
	    /* Cool, found a match.  Add it to the list.  This makes a
	     * lot more sense to me (Chris) this way... */
2109
2110
2111

#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
	     * 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. */
	    char *tmp2 = charalloc(strlen(dirname) +
		strlen(next->d_name) + 1);

	    sprintf(tmp2, "%s%s", dirname, next->d_name);
	    if (check_operating_dir(tmp2, TRUE)) {
		free(tmp2);
		continue;
2123
	    }
2124
	    free(tmp2);
2125
2126
#endif

2127
2128
2129
2130
	    matches = (char **)nrealloc(matches, (*num_matches + 1) *
		sizeof(char *));
	    matches[*num_matches] = mallocstrcpy(NULL, next->d_name);
	    ++(*num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2131
2132
	}
    }
2133
2134
    closedir(dir);
    free(dirname);
2135
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2136

2137
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2138
2139
}

2140
2141
2142
/* Do tab completion.  This function now has an arg which refers to how
 * much the statusbar cursor position (place) should be advanced. */
char *input_tab(char *buf, size_t *place, bool *lastwastab, bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2143
{
2144
2145
    size_t num_matches = 0;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2146

2147
    assert(buf != NULL && place != NULL && *place <= strlen(buf) && lastwastab != NULL && list != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2148

2149
    *list = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2150

2151
2152
2153
2154
    /* If the word starts with `~' and there is no slash in the word,
     * then try completing this word as a username. */
    if (*place > 0 && *buf == '~') {
	const char *bob = strchr(buf, '/');
Chris Allegretta's avatar
Chris Allegretta committed
2155

2156
2157
2158
2159
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2160

2161
2162
2163
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
	matches = cwd_tab_completion(buf, &num_matches, *place);
2164

2165
2166
2167
2168
2169
    if (num_matches <= 0)
	beep();
    else {
	size_t match, common_len = 0;
	char *mzero;
2170
2171
2172
	const char *lastslash = revstrstr(buf, "/", buf + *place);
	size_t lastslash_len = (lastslash == NULL) ? 0 :
		lastslash - buf + 1;
2173
2174
2175
2176
2177
2178
2179

	while (TRUE) {
	    for (match = 1; match < num_matches; match++) {
		if (matches[0][common_len] !=
			matches[match][common_len])
		    break;
	    }
2180

2181
	    if (match < num_matches || matches[0][common_len] == '\0')
2182
		break;
2183

2184
2185
	    common_len++;
	}
2186

2187
2188
	mzero = charalloc(lastslash_len + common_len + 1);
	sprintf(mzero, "%.*s%.*s", lastslash_len, buf, common_len,
2189
		matches[0]);
2190

2191
	common_len += lastslash_len;
2192

2193
	assert(common_len >= *place);
2194

2195
2196
2197
2198
2199
	if (num_matches == 1 && is_dir(mzero)) {
	    mzero[common_len] = '/';
	    common_len++;
	    assert(common_len > *place);
	}
2200

2201
2202
2203
	if (num_matches > 1 && (common_len != *place ||
		*lastwastab == FALSE))
	    beep();
2204

2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
	/* If there is more match to display on the statusbar, show it.
	 * We reset lastwastab to FALSE: it requires hitting Tab twice
	 * in succession with no statusbar changes to see a match
	 * list. */
	if (common_len != *place) {
	    size_t buflen = strlen(buf);

	    *lastwastab = FALSE;
	    buf = charealloc(buf, common_len + buflen - *place + 1);
	    charmove(buf + common_len, buf + *place, buflen - *place + 1);
	    strncpy(buf, mzero, common_len);
	    *place = common_len;
	} else if (*lastwastab == FALSE || num_matches < 2)
	    *lastwastab = TRUE;
	else {
	    int longest_name = 0, editline = 0;
	    size_t columns;
2222

2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
	    /* Now we show a list of the available choices. */
	    assert(num_matches > 1);

	    /* Sort the list. */
	    qsort(matches, num_matches, sizeof(char *), diralphasort);

	    for (match = 0; match < num_matches; match++) {
		common_len = strnlenpt(matches[match], COLS - 1);
		if (common_len > COLS - 1) {
		    longest_name = COLS - 1;
2233
2234
		    break;
		}
2235
2236
		if (common_len > longest_name)
		    longest_name = common_len;
2237
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2238

2239
	    assert(longest_name <= COLS - 1);
Chris Allegretta's avatar
Chris Allegretta committed
2240

2241
2242
2243
2244
	    /* Each column will be longest_name + 2 characters wide,
	     * i.e, two spaces between columns, except that there will
	     * be only one space after the last column. */
	    columns = (COLS + 1) / (longest_name + 2);
2245

2246
2247
2248
2249
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2250

2251
2252
	    /* Disable el cursor. */
	    curs_set(0);
2253

2254
2255
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2256

2257
2258
		wmove(edit, editline, (longest_name + 2) *
			(match % columns));
2259

2260
2261
2262
2263
2264
		if (match % columns == 0 && editline == editwinrows - 1
			&& num_matches - match > columns) {
		    waddstr(edit, _("(more)"));
		    break;
		}
2265

2266
2267
2268
2269
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2270

2271
		if ((match + 1) % columns == 0)
2272
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2273
2274
	    }
	    wrefresh(edit);
2275
	    *list = TRUE;
2276
2277
2278
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2279
2280
    }

2281
2282
    free_charptrarray(matches, num_matches);

2283
    /* Only refresh the edit window if we don't have a list of filename
2284
     * matches on it. */
2285
    if (*list == FALSE)
2286
	edit_refresh();
2287
2288

    /* Enable el cursor. */
2289
    curs_set(1);
2290

2291
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2292
}
2293
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2294

2295
2296
/* Only print the last part of a path.  Isn't there a shell command for
 * this? */
2297
2298
const char *tail(const char *foo)
{
2299
    const char *tmp = strrchr(foo, '/');
2300

2301
2302
2303
    if (tmp == NULL)
	tmp = foo;
    else if (*tmp == '/')
2304
2305
2306
2307
2308
	tmp++;

    return tmp;
}

2309
#ifndef DISABLE_BROWSER
2310
/* Free our malloc()ed memory. */
2311
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2312
{
2313
2314
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2315
2316
2317
    free(array);
}

2318
2319
/* Strip one directory from the end of path. */
void striponedir(char *path)
Chris Allegretta's avatar
Chris Allegretta committed
2320
{
2321
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2322

2323
    assert(path != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2324

2325
    tmp = strrchr(path, '/');
2326
2327
    if (tmp != NULL)
 	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2328
2329
}

2330
2331
2332
2333
2334
2335
/* Return a list of files contained in the directory path.  *longest is
 * the maximum display length of a file, up to COLS - 1 (but at least
 * 7).  *numents is the number of files.  We assume path exists and is a
 * directory.  If neither is true, we return NULL. */
char **browser_init(const char *path, int *longest, size_t *numents, DIR
	*dir)
2336
{
2337
2338
2339
    const struct dirent *next;
    char **filelist;
    size_t i, path_len;
2340

2341
    assert(dir != NULL);
2342

2343
    *longest = 0;
2344

2345
    i = 0;
2346
2347

    while ((next = readdir(dir)) != NULL) {
2348
2349
2350
	size_t dlen;

	/* Don't show the . entry. */
2351
	if (strcmp(next->d_name, ".") == 0)
2352
	   continue;
2353
2354
2355
2356
2357
	i++;

	dlen = strlenpt(next->d_name);
	if (dlen > *longest)
	    *longest = dlen;
2358
    }
2359
2360

    *numents = i;
2361
2362
2363
    rewinddir(dir);
    *longest += 10;

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

2366
2367
    path_len = strlen(path);

2368
2369
2370
2371
    i = 0;

    while ((next = readdir(dir)) != NULL && i < *numents) {
	/* Don't show the "." entry. */
2372
	if (strcmp(next->d_name, ".") == 0)
2373
2374
	   continue;

2375
2376
	filelist[i] = charalloc(path_len + strlen(next->d_name) + 1);
	sprintf(filelist[i], "%s%s", path, next->d_name);
2377
2378
	i++;
    }
2379
2380
2381
2382
2383

    /* Maybe the number of files in the directory changed between the
     * first time we scanned and the second.  i is the actual length of
     * filelist, so record it. */
    *numents = i;
2384
    closedir(dir);
2385
2386
2387

    if (*longest > COLS - 1)
	*longest = COLS - 1;
2388
2389
    if (*longest < 7)
	*longest = 7;
2390
2391
2392
2393

    return filelist;
}

2394
2395
2396
/* Our browser function.  path is the path to start browsing from.
 * Assume path has already been tilde-expanded. */
char *do_browser(char *path, DIR *dir)
Chris Allegretta's avatar
Chris Allegretta committed
2397
{
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
    int kbinput, longest, selected, width;
    bool meta_key, func_key, old_constupdate = ISSET(CONSTUPDATE);
    size_t numents;
    char **filelist, *retval = NULL;

    curs_set(0);
    blank_statusbar();
    bottombars(browser_list);
    wrefresh(bottomwin);

#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
    /* Set currshortcut so the user can click in the shortcut area, and
     * so the browser help screen will come up. */
    currshortcut = browser_list;
2412
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2413

2414
    UNSET(CONSTUPDATE);
2415

2416
2417
2418
2419
2420
2421
2422
2423
  change_browser_directory:
	/* We go here after the user selects a new directory. */

    kbinput = ERR;
    selected = 0;
    width = 0;

    path = mallocstrassn(path, get_full_path(path));
Chris Allegretta's avatar
Chris Allegretta committed
2424

2425
2426
    /* Assume that path exists and ends with a slash. */
    assert(path != NULL && path[strlen(path) - 1] == '/');
Chris Allegretta's avatar
Chris Allegretta committed
2427

2428
2429
2430
2431
    /* Get the list of files. */
    filelist = browser_init(path, &longest, &numents, dir);

    assert(filelist != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2432

2433
    /* Sort the list. */
Chris Allegretta's avatar
Chris Allegretta committed
2434
2435
2436
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
Chris Allegretta's avatar
Chris Allegretta committed
2437
2438

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2439
    do {
2440
	bool abort = FALSE;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2441
	int j, col = 0, editline = 0, fileline;
2442
2443
2444
2445
	int filecols = 0;
	    /* Used only if width == 0, to calculate the number of files
	     * per row below. */
	struct stat st;
2446
2447
	char *new_path;
	    /* Used by the Go To Directory prompt. */
2448
2449
2450
#ifndef DISABLE_MOUSE
	MEVENT mevent;
#endif
Rocco Corsi's avatar
   
Rocco Corsi committed
2451

2452
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2453

2454
2455
	/* Compute the line number we're on now, so that we don't divide
	 * by zero later. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2456
	fileline = selected;
2457
	if (width != 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2458
	    fileline /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2459
2460

	switch (kbinput) {
2461
#ifndef DISABLE_MOUSE
2462
2463
2464
	    case KEY_MOUSE:
		if (getmouse(&mevent) == ERR)
		    break;
2465

2466
2467
2468
2469
2470
2471
2472
2473
2474
		/* If we clicked in the edit window, we probably clicked
		 * on a file. */
		if (wenclose(edit, mevent.y, mevent.x)) {
		    int selectedbackup = selected;

		    mevent.y -= 2;

		    /* longest is the width of each column.  There are
		     * two spaces between each column. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2475
		    selected = (fileline / editwinrows) * editwinrows *
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
			width + 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--;

		    /* If we're off the screen, reset to the last item.
		     * If we clicked the same place as last time, select
		     * this name! */
		    if (selected > numents - 1)
			selected = numents - 1;
		    else if (selectedbackup == selected)
			/* Put back the 'select' key. */
			unget_kbinput('s', FALSE, FALSE);
		} else {
		    /* We must have clicked a shortcut.  Put back the
		     * equivalent shortcut key. */
		    int mouse_x, mouse_y;
		    get_mouseinput(&mouse_x, &mouse_y, TRUE);
		}
2498

2499
		break;
2500
#endif
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
	    case NANO_PREVLINE_KEY:
		if (selected >= width)
		    selected -= width;
		break;
	    case NANO_BACK_KEY:
		if (selected > 0)
		    selected--;
		break;
	    case NANO_NEXTLINE_KEY:
		if (selected + width <= numents - 1)
		    selected += width;
		break;
	    case NANO_FORWARD_KEY:
		if (selected < numents - 1)
		    selected++;
		break;
	    case NANO_PREVPAGE_KEY:
	    case NANO_PREVPAGE_FKEY:
	    case '-': /* Pico compatibility. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2520
		if (selected >= (editwinrows + fileline % editwinrows) *
2521
			width)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2522
		    selected -= (editwinrows + fileline % editwinrows) *
2523
2524
2525
2526
2527
2528
2529
			width;
		else
		    selected = 0;
		break;
	    case NANO_NEXTPAGE_KEY:
	    case NANO_NEXTPAGE_FKEY:
	    case ' ': /* Pico compatibility. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2530
		selected += (editwinrows - fileline % editwinrows) *
2531
2532
2533
2534
2535
2536
2537
			width;
		if (selected >= numents)
		    selected = numents - 1;
		break;
	    case NANO_HELP_KEY:
	    case NANO_HELP_FKEY:
	    case '?': /* Pico compatibility. */
2538
#ifndef DISABLE_HELP
2539
2540
		do_help();
		curs_set(0);
2541
#else
2542
		nano_disabled_msg();
2543
#endif
Rocco Corsi's avatar
   
Rocco Corsi committed
2544
		break;
2545
2546
2547
2548
2549
2550
2551
2552
2553
	    case NANO_ENTER_KEY:
	    case 'S': /* Pico compatibility. */
	    case 's':
		/* You can't move up from "/". */
		if (strcmp(filelist[selected], "/..") == 0) {
		    statusbar(_("Can't move up a directory"));
		    beep();
		    break;
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2554

2555
#ifndef DISABLE_OPERATINGDIR
2556
2557
2558
2559
2560
2561
		/* Note: the selected file can be outside the operating
		 * directory if it's ".." or if it's a symlink to a
		 * directory outside the operating directory. */
		if (check_operating_dir(filelist[selected], FALSE)) {
		    statusbar(
			_("Can't go outside of %s in restricted mode"),
2562
			operating_dir);
2563
2564
2565
		    beep();
		    break;
		}
2566
2567
#endif

2568
2569
2570
2571
2572
2573
		if (stat(filelist[selected], &st) == -1) {
		    statusbar(_("Error reading %s: %s"),
			filelist[selected], strerror(errno));
		    beep();
		    break;
		}
2574

2575
2576
2577
2578
2579
		if (!S_ISDIR(st.st_mode)) {
		    retval = mallocstrcpy(retval, filelist[selected]);
		    abort = TRUE;
		    break;
		}
2580

2581
2582
2583
2584
2585
2586
2587
		dir = opendir(filelist[selected]);
		if (dir == NULL) {
		    /* We can't open this dir for some reason.
		     * Complain. */
		    statusbar(_("Error reading %s: %s"),
			filelist[selected], strerror(errno));
		    break;
Chris Allegretta's avatar
Chris Allegretta committed
2588
		}
Chris Allegretta's avatar
Chris Allegretta committed
2589

2590
		path = mallocstrcpy(path, filelist[selected]);
2591

2592
2593
2594
2595
		/* Start over again with the new path value. */
		free_charptrarray(filelist, numents);
		goto change_browser_directory;

2596
2597
2598
2599
2600
	    /* Refresh the screen. */
	    case NANO_REFRESH_KEY:
		total_update();
		break;

2601
2602
2603
2604
2605
2606
2607
2608
	    /* Go to a specific directory. */
	    case NANO_GOTOLINE_KEY:
	    case NANO_GOTOLINE_FKEY:
	    case 'G': /* Pico compatibility. */
	    case 'g':
		curs_set(1);

		j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2609
#ifndef NANO_SMALL
2610
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2611
#endif
2612
			_("Go To Directory"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2613

2614
2615
		curs_set(0);
		bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2616

2617
2618
2619
2620
		if (j < 0) {
		    statusbar(_("Cancelled"));
		    break;
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2621

2622
2623
2624
2625
2626
2627
2628
		new_path = real_dir_from_tilde(answer);

		if (new_path[0] != '/') {
		    new_path = charealloc(new_path, strlen(path) +
			strlen(answer) + 1);
		    sprintf(new_path, "%s%s", path, answer);
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2629

2630
#ifndef DISABLE_OPERATINGDIR
2631
2632
2633
2634
2635
2636
2637
		if (check_operating_dir(new_path, FALSE)) {
		    statusbar(
			_("Can't go outside of %s in restricted mode"),
			operating_dir);
		    free(new_path);
		    break;
		}
2638
2639
#endif

2640
2641
2642
2643
2644
2645
2646
2647
2648
		dir = opendir(new_path);
		if (dir == NULL) {
		    /* We can't open this dir for some reason.
		     * Complain. */
		    statusbar(_("Error reading %s: %s"), answer,
			strerror(errno));
		    free(new_path);
		    break;
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2649

2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
		/* Start over again with the new path value. */
		free(path);
		path = new_path;
		free_charptrarray(filelist, numents);
		goto change_browser_directory;

	    /* Abort the browser. */
	    case NANO_EXIT_KEY:
	    case NANO_EXIT_FKEY:
	    case 'E': /* Pico compatibility. */
	    case 'e':
		abort = TRUE;
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2663
	}
2664

Chris Allegretta's avatar
Chris Allegretta committed
2665
2666
2667
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2668
2669
	blank_edit();

2670
	if (width != 0)
2671
2672
	    j = width * editwinrows *
		((selected / width) / editwinrows);
Chris Allegretta's avatar
Chris Allegretta committed
2673
	else
2674
	    j = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2675
2676
2677

	wmove(edit, 0, 0);

2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
	{
	    int foo_len = mb_cur_max() * 7;
	    char *foo = charalloc(foo_len + 1);

	    for (; j < numents && editline <= editwinrows - 1; j++) {
		char *disp = display_string(tail(filelist[j]), 0,
			longest, FALSE);

		/* Highlight the currently selected file/dir. */
		if (j == selected)
		    wattron(edit, A_REVERSE);

		mvwaddnstr(edit, editline, col, hblank, longest);
		mvwaddstr(edit, editline, col, disp);
		free(disp);

		col += longest;
		filecols++;

		/* Show file info also.  We don't want to report file
		 * sizes for links, so we use lstat().  Also, stat() and
		 * lstat() return an error if, for example, the file is
		 * deleted while the file browser is open.  In that
		 * case, we report "--" as the file info. */
		if (lstat(filelist[j], &st) == -1 ||
			S_ISLNK(st.st_mode)) {
		    /* Aha!  It's a symlink!  Now, is it a dir?  If so,
		     * mark it as such. */
		    if (stat(filelist[j], &st) == 0 &&
			S_ISDIR(st.st_mode)) {
			strncpy(foo, _("(dir)"), foo_len);
			foo[foo_len] = '\0';
		    } else
			strcpy(foo, "--");
		} else if (S_ISDIR(st.st_mode))
		    strncpy(foo, _("(dir)"), foo_len);
		else if (st.st_size < (1 << 10)) /* less than 1 k. */
		    sprintf(foo, "%4u  B", (unsigned int)st.st_size);
		else if (st.st_size < (1 << 20)) /* less than 1 meg. */
		    sprintf(foo, "%4u KB",
			(unsigned int)(st.st_size >> 10));
		else if (st.st_size < (1 << 30)) /* less than 1 gig. */
		    sprintf(foo, "%4u MB",
			(unsigned int)(st.st_size >> 20));
		else
		    sprintf(foo, "%4u GB",
			(unsigned int)(st.st_size >> 30));

		mvwaddnstr(edit, editline, col - strlen(foo), foo,
			foo_len);

		if (j == selected)
		    wattroff(edit, A_REVERSE);

		/* Add some space between the columns. */
		col += 2;

		/* If the next entry isn't going to fit on the line,
		 * move to the next line. */
		if (col > COLS - longest) {
		    editline++;
		    col = 0;
		    if (width == 0)
			width = filecols;
		}
		wmove(edit, editline, col);
Chris Allegretta's avatar
Chris Allegretta committed
2744
	    }
2745
2746

	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2747
	}
2748

2749
	wrefresh(edit);
2750
2751
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
2752

Chris Allegretta's avatar
Chris Allegretta committed
2753
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2754
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2755
    edit_refresh();
2756
2757
2758
    curs_set(1);
    if (old_constupdate)
	SET(CONSTUPDATE);
Chris Allegretta's avatar
Chris Allegretta committed
2759

2760
    /* Clean up. */
Chris Allegretta's avatar
Chris Allegretta committed
2761
    free_charptrarray(filelist, numents);
2762
2763
    free(path);

Chris Allegretta's avatar
Chris Allegretta committed
2764
2765
    return retval;
}
2766

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2767
2768
2769
/* The file browser front end.  We check to see if inpath has a dir in
 * it.  If it does, we start do_browser() from there.  Otherwise, we
 * start do_browser() from the current directory. */
2770
char *do_browse_from(const char *inpath)
2771
2772
{
    struct stat st;
2773
    char *path;
2774
	/* This holds the tilde-expanded version of inpath. */
2775
    DIR *dir = NULL;
2776

2777
2778
2779
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2780

2781
2782
2783
2784
2785
2786
    /* Perhaps path is a directory.  If so, we'll pass it to
     * do_browser().  Or perhaps path is a directory / a file.  If so,
     * we'll try stripping off the last path element and passing it to
     * do_browser().  Or perhaps path doesn't have a directory portion
     * at all.  If so, we'll just pass the current directory to
     * do_browser(). */
2787
2788
    if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	striponedir(path);
2789
2790
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2791

2792
2793
	    path = charalloc(PATH_MAX + 1);
	    path = getcwd(path, PATH_MAX + 1);
2794
2795
2796

	    if (path != NULL)
		align(&path);
2797
	}
2798
    }
2799

2800
#ifndef DISABLE_OPERATINGDIR
2801
2802
2803
2804
2805
2806
2807
    /* If the resulting path isn't in the operating directory, use
     * the operating directory instead. */
    if (check_operating_dir(path, FALSE)) {
	if (path != NULL)
	    free(path);
	path = mallocstrcpy(NULL, operating_dir);
    }
2808
2809
#endif

2810
2811
2812
    if (path != NULL)
	dir = opendir(path);

2813
    if (dir == NULL) {
2814
	beep();
2815
2816
2817
2818
2819
	free(path);
	return NULL;
    }

    return do_browser(path, dir);
2820
}
Chris Allegretta's avatar
Chris Allegretta committed
2821
#endif /* !DISABLE_BROWSER */
2822

2823
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2824
2825
2826
/* Return $HOME/.nano_history, or NULL if we can't find the homedir.
 * The string is dynamically allocated, and should be freed. */
char *histfilename(void)
2827
{
2828
    char *nanohist = NULL;
2829

2830
2831
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2832

2833
2834
2835
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2836
    }
2837
2838
2839
2840
2841
2842
    return nanohist;
}

void load_history(void)
{
    char *nanohist = histfilename();
Chris Allegretta's avatar
Chris Allegretta committed
2843

2844
    /* assume do_rcfile() has reported missing home dir */
2845
2846
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2847

2848
	if (hist == NULL) {
2849
	    if (errno != ENOENT) {
2850
2851
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2852
2853
2854
2855
		rcfile_error(N_("Error reading %s: %s"), nanohist,
			strerror(errno));
		fprintf(stderr,
			_("\nPress Return to continue starting nano\n"));
2856
2857
		while (getchar() != '\n')
		    ;
2858
	    }
2859
	} else {
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
	    historyheadtype *history = &search_history;
	    char *line = NULL;
	    size_t buflen = 0;
	    ssize_t read;

	    while ((read = getline(&line, &buflen, hist)) >= 0) {
		if (read > 0 && line[read - 1] == '\n') {
		    read--;
		    line[read] = '\0';
		}
		if (read > 0) {
		    unsunder(line, read);
		    update_history(history, line);
		} else
2874
2875
2876
		    history = &replace_history;
	    }
	    fclose(hist);
2877
	    free(line);
2878
2879
	    UNSET(HISTORY_CHANGED);
	}
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
	free(nanohist);
    }
}

bool writehist(FILE *hist, historyheadtype *histhead)
{
    historytype *h;

    /* write oldest first */
    for (h = histhead->tail; h->prev != NULL; h = h->prev) {
	size_t len = strlen(h->data);

	sunder(h->data);
	if (fwrite(h->data, sizeof(char), len, hist) < len ||
		putc('\n', hist) == EOF)
	    return FALSE;
2896
    }
2897
    return TRUE;
2898
2899
2900
2901
2902
}

/* save histories to ~/.nano_history */
void save_history(void)
{
2903
    char *nanohist;
2904
2905

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

2910
2911
2912
2913
    nanohist = histfilename();

    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "wb");
Chris Allegretta's avatar
Chris Allegretta committed
2914

2915
	if (hist == NULL)
2916
2917
	    rcfile_error(N_("Error writing %s: %s"), nanohist,
		strerror(errno));
2918
	else {
2919
2920
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2921
2922
2923
2924

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
2925
2926
		rcfile_error(N_("Error writing %s: %s"), nanohist,
			strerror(errno));
2927
2928
2929
2930
2931
	    fclose(hist);
	}
	free(nanohist);
    }
}
2932
#endif /* !NANO_SMALL && ENABLE_NANORC */