files.c 76.1 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
38
#include "proto.h"
#include "nano.h"
Chris Allegretta's avatar
Chris Allegretta committed
39

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

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

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

62
63
64
65
66
67
/* 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
68
{
69
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
70

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

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

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

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

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

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

    return fileptr;
}

113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/* 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
129
{
130
131
    size_t num_lines = 0;
	/* The number of lines in the file. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
132
133
    size_t num_chars;
	/* The number of characters in the file. */
134
135
136
137
138
139
140
141
    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
142
    char *buf;
143
144
145
146
147
148
149
150
	/* 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. */
151
#ifndef NANO_SMALL
152
    int format = 0;
153
	/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
154
#endif
Chris Allegretta's avatar
Chris Allegretta committed
155

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

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

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

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

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

180
181
	/* If it's a *nix file (LF) or a DOS file (CR LF), and file
	 * conversion isn't disabled, handle it! */
182
	if (input == '\n') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
183

184
#ifndef NANO_SMALL
185
186
	    /* 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
187
188
	     * currently think it's a Mac file. */
	    if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r' &&
189
190
		(format == 0 || format == 2))
		format++;
191
192
193
194
#endif

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

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

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

208
209
210
211
212
	    /* 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;
213
214
215

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
232
	    /* Now we allocate a bigger buffer 128 characters at a time.
233
234
235
	     * 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
236
237
	    if (i >= bufx - 1) {
		bufx += 128;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
238
		buf = charealloc(buf, bufx);
Chris Allegretta's avatar
Chris Allegretta committed
239
	    }
240

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

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

253
#ifndef NANO_SMALL
254
255
256
    /* 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') {
257
258
	len = 1;

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

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

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

281
282
283
284
    free(buf);

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

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

303
304
305
    get_totals(fileage, filebot, NULL, &num_chars);
    totsize += num_chars;

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

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

330
331
332
333
334
335
336
337
/* 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
338
339
340
341
{
    int fd;
    struct stat fileinfo;

342
    assert(f != NULL);
343

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

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

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

386
    buf = charalloc(namelen + num_of_digits(LONG_MAX) + 7);
387
    strcpy(buf, name);
388
389
    strcpy(buf + namelen, ".save");
    namelen += 5;
390

391
    while (TRUE) {
392
	struct stat fs;
393
394

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

	i++;
400
	sprintf(buf + namelen, ".%ld", i);
401
402
    }

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

406
407
408
    return buf;
}

409
410
#ifndef NANO_SMALL
void execute_command(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
411
{
412
413
414
415
416
417
418
#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);
419
    }
420
421
422
423
424
425
426
427
428
#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
429

430
431
432
433
/* 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
434
    bool new_buffer = (fileage == NULL
435
436
#ifdef ENABLE_MULTIBUFFER
	 || ISSET(MULTIBUFFER)
437
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
438
	);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
439
440
441
442
	/* 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. */
443
444
445
446
    FILE *f;
    int rc;
	/* rc == -2 means that the statusbar displayed "New File".  -1
	 * means that the open failed.  0 means success. */
447

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

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

461
462
463
464
465
    rc = open_file(name, new_buffer, &f);

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

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

    if (rc == 0) {
478
479
	file_format fmt_save = fmt;

480
	read_file(f, filename);
481
482
483
484
485
486

	/* 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
487
#ifndef NANO_SMALL
488
	stat(filename, &originalfilestat);
Chris Allegretta's avatar
Chris Allegretta committed
489
#endif
490
491
492
493
494
495
496
497
    }

    /* 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();
}

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

515
#ifndef DISABLE_WRAPPING
516
    wrap_reset();
517
#endif
518

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

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

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

567
	    ans = mallocstrcpy(ans, answer);
Chris Allegretta's avatar
Chris Allegretta committed
568

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

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

582
583
		if (tmp == NULL)
		    continue;
584

585
586
		free(answer);
		answer = tmp;
587
588
589
590

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

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

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

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

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

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

642
643
644
645
646
647
648
649
650
		/* 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
651
		/* If we didn't insert into a new buffer, unpartition
652
653
654
655
		 * 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. */
656
		unpartition_filestruct(&filepart);
657

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

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

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

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

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

684
685
686
687
688
	    /* Refresh the screen. */
	    edit_refresh();

	    break;
	}
689
    }
Chris Allegretta's avatar
Chris Allegretta committed
690

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

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

    display_main_list();
}

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

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

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

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

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

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

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

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

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

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

776
777
778
779
780
781
782
783
784
    /* 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);
785
786
787
	open_files = open_files->next;
    }

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

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

796
797
798
799
800
801
802
803
804
    /* 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;
805

806
807
    /* Save the current cursor position. */
    open_files->current_x = current_x;
808

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

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

815
816
    /* Save the current total size. */
    open_files->totsize = totsize;
817

818
819
    /* Start with no flags saved. */
    open_files->flags = 0;
820

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
825
#ifndef NANO_SMALL
826
827
828
829
830
    /* 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
831
832
    }

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

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

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

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

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

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

860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
    /* 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
880
881
882
883
884
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

#ifndef NANO_SMALL
885
886
887
888
    /* 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
889
890
891
	SET(MARK_ISSET);
    } else
	UNSET(MARK_ISSET);
892

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

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

902
    /* Update the titlebar. */
903
904
905
906
    clearok(topwin, FALSE);
    titlebar(NULL);
}

907
/* Open either the next or previous file buffer. */
908
void open_prevnext_file(bool next)
909
{
910
    add_open_file(TRUE);
911

912
    assert(open_files != NULL);
913

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

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

#ifdef DEBUG
926
    fprintf(stderr, "filename is %s\n", open_files->filename);
927
928
#endif

929
    /* Load the file we switched to. */
930
931
    load_open_file();

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

937
938
939
940
941
#ifdef DEBUG
    dump_buffer(current);
#endif
}

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

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

956
957
/* 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
958
 * there are no more open file buffers. */
959
bool close_open_file(void)
960
{
961
    assert(open_files != NULL);
962

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

967
968
    /* Open the next file. */
    open_nextfile_void();
Chris Allegretta's avatar
Chris Allegretta committed
969

970
971
    /* Close the file we had open before. */
    unlink_opennode(open_files->prev);
972

973
    /* Reinitialize the shortcut list. */
974
    shortcut_init(FALSE);
975
    display_main_list();
976

977
    return TRUE;
978
}
979
#endif /* ENABLE_MULTIBUFFER */
980

981
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
982
/* When passed "[relative path]" or "[relative path][filename]" in
983
 * origpath, return "[full path]" or "[full path][filename]" on success,
984
985
986
987
 * 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. */
988
char *get_full_path(const char *origpath)
989
{
990
    char *d_here, *d_there = NULL;
991

992
993
    if (origpath == NULL)
    	return NULL;
994

995
996
997
    /* Get the current directory. */
    d_here = charalloc(PATH_MAX + 1);
    d_here = getcwd(d_here, PATH_MAX + 1);
998

999
    if (d_here != NULL) {
1000
1001
1002
1003
	const char *last_slash;
	char *d_there_file = NULL;
	bool path_only;
	struct stat fileinfo;
1004

1005
1006
	align(&d_here);

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

1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
	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. */
1026
	if (path_only) {
1027
1028
1029
1030
	    size_t d_there_len = strlen(d_there);

	    if (d_there[d_there_len - 1] != '/') {
		d_there = charealloc(d_there, d_there_len + 2);
1031
1032
1033
1034
		strcat(d_there, "/");
	    }
	}

1035
	/* Search for the last slash in d_there. */
1036
1037
	last_slash = strrchr(d_there, '/');

1038
1039
1040
1041
	/* 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);
1042

1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
	    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) {
1058
		free(d_there);
1059
1060
1061
1062
		d_there = NULL;
	    } else {
		/* Get the full path and save it in d_there. */
		free(d_there);
1063

1064
1065
1066
		d_there = charalloc(PATH_MAX + 1);
		d_there = getcwd(d_there, PATH_MAX + 1);

1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
		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
1077
1078
1079
1080
1081
1082
1083
1084
		    /* 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);
1085
1086
	    }

1087
1088
	    /* Free d_here, since we're done using it. */
	    free(d_here);
1089
	}
1090

1091
1092
1093
1094
1095
	/* 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. */
1096
	if (!path_only && d_there != NULL) {
1097
1098
1099
1100
1101
1102
	    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. */
1103
1104
1105
	free(d_there_file);
    }

1106
    return d_there;
1107
}
1108
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1109
1110

#ifndef DISABLE_SPELLER
1111
1112
1113
/* 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. */
1114
char *check_writable_directory(const char *path)
1115
{
1116
1117
    char *full_path = get_full_path(path);

1118
    /* If get_full_path() fails, return NULL. */
1119
    if (full_path == NULL)
1120
	return NULL;
1121

1122
1123
1124
1125
    /* 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] != '/') {
1126
	free(full_path);
1127
	return NULL;
1128
    }
1129
1130
1131
1132
1133

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

1134
1135
1136
1137
/* 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)
1138
{
1139
1140
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1141
    int filedesc;
1142

1143
1144
1145
    /* 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. */
1146
    TMPDIR_env = getenv("TMPDIR");
1147
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1148
	full_tempdir = check_writable_directory(TMPDIR_env);
1149

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

1155
1156
1157
    /* if P_tmpdir is NULL, use /tmp. */
    if (full_tempdir == NULL)
	full_tempdir = mallocstrcpy(NULL, "/tmp/");
1158

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

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

    free(full_tempdir);
1172

1173
    return NULL;
1174
1175
}
#endif /* !DISABLE_SPELLER */
1176
1177

#ifndef DISABLE_OPERATINGDIR
1178
1179
1180
1181
1182
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1183
    if (operating_dir == NULL)
1184
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1185

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

1198
1199
1200
1201
1202
/* 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)
1203
{
1204
1205
1206
1207
    /* 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. */
1208

1209
    char *fullpath;
1210
    bool retval = FALSE;
1211
    const char *whereami1, *whereami2 = NULL;
1212

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

1217
    assert(full_operating_dir != NULL);
1218
1219

    fullpath = get_full_path(currpath);
1220
1221

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

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

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

    /* Otherwise, we're still inside it. */
1245
    return retval;
1246
}
1247
1248
#endif

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

1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
/* Read from inn, write to out.  We assume inn is opened for reading,
 * and out for writing.  We return 0 on success, -1 on read error, -2 on
 * write error. */
int copy_file(FILE *inn, FILE *out)
{
    char buf[BUFSIZ];
    size_t charsread;
    int retval = 0;

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

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;
1350
1351
    if (!tmp)
	titlebar(NULL);
1352

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

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

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

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

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

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

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

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

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

1401
	/* Open the original file to copy to the backup. */
1402
	f = fopen(realname, "rb");
1403
	if (f == NULL) {
1404
	    statusbar(_("Error reading %s: %s"), realname,
1405
		strerror(errno));
1406
	    goto cleanup_and_exit;
1407
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
	/* 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);
	}
1441

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

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

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

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

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

1490
1491
    original_umask = umask(0);
    umask(original_umask);
1492

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

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

1520
1521
1522
1523
1524
1525
1526
	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
1527
1528
	    statusbar(_("Error reading %s: %s"), realname,
		strerror(errno));
1529
1530
1531
1532
1533
1534
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

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

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

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

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

1567
    /* There might not be a magicline.  There won't be when writing out
1568
1569
1570
1571
1572
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1573

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

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

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

1582
	if (size < data_len) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1583
1584
	    statusbar(_("Error writing %s: %s"), realname,
		strerror(errno));
1585
	    fclose(f);
1586
	    goto cleanup_and_exit;
1587
	}
1588
#ifndef NANO_SMALL
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1589
	if (fmt == DOS_FILE || fmt == MAC_FILE) {
1590
	    if (putc('\r', f) == EOF) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1591
1592
		statusbar(_("Error writing %s: %s"), realname,
			strerror(errno));
1593
1594
1595
		fclose(f);
		goto cleanup_and_exit;
	    }
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1596
	}
Chris Allegretta's avatar
Chris Allegretta committed
1597

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

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

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

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

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

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

    retval = 1;

  cleanup_and_exit:
    free(realname);
1667
    free(tempname);
1668
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1669
1670
}

1671
1672
1673
#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
1674
1675
1676
1677
 * 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. */
1678
int write_marked(const char *name, bool tmp, int append)
1679
1680
{
    int retval = -1;
1681
    bool old_modified = ISSET(MODIFIED);
1682
	/* write_file() unsets the MODIFIED flag. */
1683
1684
1685
1686
1687
1688
1689
1690
    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,
1691
	(const filestruct **)&bot, &bot_x, NULL);
1692
    filepart = partition_filestruct(top, top_x, bot, bot_x);
1693
1694

    /* If the line at filebot is blank, treat it as the magicline and
1695
1696
1697
1698
1699
     * 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();
1700

1701
    retval = write_file(name, tmp, append, TRUE);
1702

1703
1704
1705
1706
1707
1708
    /* If we added a magicline, remove it now. */
    if (added_magicline)
	remove_magicline();

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

1711
    if (old_modified)
1712
1713
1714
1715
1716
1717
	set_modified();

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

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

1728
    currshortcut = writefile_list;
1729

1730
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1731
1732
1733
1734
1735
	retval = write_file(filename, FALSE, 0, FALSE);

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

1738
#ifndef NANO_SMALL
1739
    if (ISSET(MARK_ISSET) && !exiting)
1740
	ans = mallocstrcpy(NULL, "");
1741
1742
    else
#endif
1743
	ans = mallocstrcpy(NULL, filename);
1744
1745
1746
1747

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

1750
	if (fmt == DOS_FILE)
1751
	   formatstr = N_(" [DOS Format]");
1752
	else if (fmt == MAC_FILE)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1753
	   formatstr = N_(" [Mac Format]");
1754
1755
1756
	else
	   formatstr = "";

1757
	if (ISSET(BACKUP_FILE))
1758
	   backupstr = N_(" [Backup]");
1759
1760
1761
	else
	   backupstr = "";

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

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

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

1798
#ifndef DISABLE_BROWSER
1799
1800
	    if (i == NANO_TOFILES_KEY) {
		char *tmp = do_browse_from(answer);
1801

1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
		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
1813
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1814
#ifndef NANO_SMALL
1815
	    if (i == TOGGLE_DOS_KEY) {
1816
		fmt = (fmt == DOS_FILE) ? NIX_FILE : DOS_FILE;
1817
1818
		continue;
	    } else if (i == TOGGLE_MAC_KEY) {
1819
		fmt = (fmt == MAC_FILE) ? NIX_FILE : MAC_FILE;
1820
1821
1822
1823
1824
		continue;
	    } else if (i == TOGGLE_BACKUP_KEY) {
		TOGGLE(BACKUP_FILE);
		continue;
	    } else
1825
#endif /* !NANO_SMALL */
1826
1827
1828
1829
1830
1831
1832
	    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
1833

Chris Allegretta's avatar
Chris Allegretta committed
1834
#ifdef DEBUG
1835
	    fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1836
#endif
1837
1838

#ifdef NANO_EXTRA
1839
1840
1841
1842
1843
1844
1845
	    if (exiting && !ISSET(TEMP_FILE) &&
		strcasecmp(answer, "zzy") == 0 && !did_cred) {
		do_credits();
		did_cred = TRUE;
		retval = -1;
		break;
	    }
1846
#endif
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
	    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'
1860
#ifndef NANO_SMALL
1861
			&& (exiting || !ISSET(MARK_ISSET))
1862
#endif
1863
			) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1864
1865
		    i = do_yesno(FALSE,
			_("Save file under DIFFERENT NAME ? "));
1866
1867
1868
		    if (i == 0 || i == -1)
			continue;
		}
1869
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1870

1871
#ifndef NANO_SMALL
1872
1873
1874
1875
1876
1877
1878
	    /* 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
1879
#endif /* !NANO_SMALL */
1880
		retval = write_file(answer, FALSE, append, FALSE);
1881

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

	    break;
	}
1891
    } /* while (TRUE) */
1892
1893

    free(ans);
1894

1895
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1896
1897
}

1898
void do_writeout_void(void)
Chris Allegretta's avatar
Chris Allegretta committed
1899
{
1900
    do_writeout(FALSE);
1901
    display_main_list();
Chris Allegretta's avatar
Chris Allegretta committed
1902
}
Chris Allegretta's avatar
Chris Allegretta committed
1903

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

1910
1911
1912
    if (buf == NULL)
    	return NULL;

1913
    if (buf[0] == '~') {
1914
	size_t i;
1915
	const char *tilde_dir;
1916

1917
	/* Figure out how much of the str we need to compare. */
1918
1919
1920
	for (i = 1; buf[i] != '/' && buf[i] != '\0'; i++)
	    ;

1921
1922
1923
1924
1925
1926
1927
	/* Get the home directory. */
	if (i == 1) {
	    get_homedir();
	    tilde_dir = homedir;
	} else {
	    const struct passwd *userdata;

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

1936
1937
1938
	if (tilde_dir != NULL) {
	    dirtmp = charalloc(strlen(tilde_dir) + strlen(buf + i) + 1);
	    sprintf(dirtmp, "%s%s", tilde_dir, buf + i);
1939
	}
1940
    }
1941

1942
1943
    /* Set a default value for dirtmp, in case the user's home directory
     * isn't found. */
1944
    if (dirtmp == NULL)
1945
	dirtmp = mallocstrcpy(NULL, buf);
1946

1947
    return dirtmp;
1948
1949
}

1950
#if !defined(DISABLE_TABCOMP) || !defined(DISABLE_BROWSER)
1951
1952
/* Our sort routine for file listings.  Sort alphabetically and
 * case-insensitively, and sort directories before filenames. */
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
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
1970
#ifndef DISABLE_TABCOMP
1971
1972
/* Is the given file a directory? */
int is_dir(const char *buf)
1973
{
1974
    char *dirptr = real_dir_from_tilde(buf);
1975
1976
    struct stat fileinfo;

1977
1978
    int ret = (stat(dirptr, &fileinfo) != -1 &&
		S_ISDIR(fileinfo.st_mode));
1979

1980
    assert(buf != NULL && dirptr != buf);
1981

1982
    free(dirptr);
1983

1984
    return ret;
1985
}
Chris Allegretta's avatar
Chris Allegretta committed
1986

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

2004
2005
2006
2007
/* 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
2008
{
2009
2010
    char **matches = NULL;
    const struct passwd *userdata;
2011

2012
    assert(buf != NULL && num_matches != NULL && buflen > 0);
2013

2014
    *num_matches = 0;
2015

2016
    while ((userdata = getpwent()) != NULL) {
2017
2018
2019
	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... */
2020

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

2028
2029
2030
2031
2032
	    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);
2033
	    ++(*num_matches);
2034
	}
2035
2036
    }
    endpwent();
2037

2038
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2039
2040
2041
}

/* This was originally called exe_n_cwd_tab_completion, but we're not
2042
2043
2044
 * 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
2045
{
2046
2047
2048
2049
2050
2051
2052
    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
2053
    DIR *dir;
2054
    const struct dirent *next;
Chris Allegretta's avatar
Chris Allegretta committed
2055

2056
    assert(dirname != NULL && num_matches != NULL && buflen >= 0);
2057

2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
    *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
2072
    } else {
2073
2074
	filename = dirname;
	dirname = mallocstrcpy(NULL, "./");
Chris Allegretta's avatar
Chris Allegretta committed
2075
2076
    }

2077
    assert(dirname[strlen(dirname) - 1] == '/');
2078

Chris Allegretta's avatar
Chris Allegretta committed
2079
    dir = opendir(dirname);
2080

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

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

Chris Allegretta's avatar
Chris Allegretta committed
2094
2095
2096
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2097
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2098
#endif
2099
2100
2101
2102
2103
2104
	/* 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... */
2105
2106
2107

#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
	     * 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;
2119
	    }
2120
	    free(tmp2);
2121
2122
#endif

2123
2124
2125
2126
	    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
2127
2128
	}
    }
2129
2130
    closedir(dir);
    free(dirname);
2131
    free(filename);
Chris Allegretta's avatar
Chris Allegretta committed
2132

2133
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2134
2135
}

2136
2137
2138
/* 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
2139
{
2140
2141
    size_t num_matches = 0;
    char **matches = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2142

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

2145
    *list = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2146

2147
2148
2149
2150
    /* 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
2151

2152
2153
2154
2155
	if (bob == NULL || bob >= buf + *place)
	    matches = username_tab_completion(buf, &num_matches,
		*place);
    }
2156

2157
2158
2159
    /* Match against files relative to the current working directory. */
    if (matches == NULL)
	matches = cwd_tab_completion(buf, &num_matches, *place);
2160

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

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

2177
	    if (match < num_matches || matches[0][common_len] == '\0')
2178
		break;
2179

2180
2181
	    common_len++;
	}
2182

2183
2184
	mzero = charalloc(lastslash_len + common_len + 1);
	sprintf(mzero, "%.*s%.*s", lastslash_len, buf, common_len,
2185
		matches[0]);
2186

2187
	common_len += lastslash_len;
2188

2189
	assert(common_len >= *place);
2190

2191
2192
2193
2194
2195
	if (num_matches == 1 && is_dir(mzero)) {
	    mzero[common_len] = '/';
	    common_len++;
	    assert(common_len > *place);
	}
2196

2197
2198
2199
	if (num_matches > 1 && (common_len != *place ||
		*lastwastab == FALSE))
	    beep();
2200

2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
	/* 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;
2218

2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
	    /* 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;
2229
2230
		    break;
		}
2231
2232
		if (common_len > longest_name)
		    longest_name = common_len;
2233
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2234

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

2237
2238
2239
2240
	    /* 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);
2241

2242
2243
2244
2245
	    /* Blank the edit window, and print the matches out
	     * there. */
	    blank_edit();
	    wmove(edit, 0, 0);
2246

2247
2248
	    /* Disable el cursor. */
	    curs_set(0);
2249

2250
2251
	    for (match = 0; match < num_matches; match++) {
		char *disp;
2252

2253
2254
		wmove(edit, editline, (longest_name + 2) *
			(match % columns));
2255

2256
2257
2258
2259
2260
		if (match % columns == 0 && editline == editwinrows - 1
			&& num_matches - match > columns) {
		    waddstr(edit, _("(more)"));
		    break;
		}
2261

2262
2263
2264
2265
		disp = display_string(matches[match], 0, longest_name,
			FALSE);
		waddstr(edit, disp);
		free(disp);
2266

2267
		if ((match + 1) % columns == 0)
2268
		    editline++;
Chris Allegretta's avatar
Chris Allegretta committed
2269
2270
	    }
	    wrefresh(edit);
2271
	    *list = TRUE;
2272
2273
2274
	}

	free(mzero);
Chris Allegretta's avatar
Chris Allegretta committed
2275
2276
    }

2277
2278
    free_charptrarray(matches, num_matches);

2279
    /* Only refresh the edit window if we don't have a list of filename
2280
     * matches on it. */
2281
    if (*list == FALSE)
2282
	edit_refresh();
2283
2284

    /* Enable el cursor. */
2285
    curs_set(1);
2286

2287
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2288
}
2289
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2290

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

2297
2298
2299
    if (tmp == NULL)
	tmp = foo;
    else if (*tmp == '/')
2300
2301
2302
2303
2304
	tmp++;

    return tmp;
}

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

2314
2315
/* Strip one directory from the end of path. */
void striponedir(char *path)
Chris Allegretta's avatar
Chris Allegretta committed
2316
{
2317
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2318

2319
    assert(path != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2320

2321
    tmp = strrchr(path, '/');
2322
2323
    if (tmp != NULL)
 	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2324
2325
}

2326
2327
2328
2329
2330
2331
/* 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)
2332
{
2333
2334
2335
    const struct dirent *next;
    char **filelist;
    size_t i, path_len;
2336

2337
    assert(dir != NULL);
2338

2339
    *longest = 0;
2340

2341
    i = 0;
2342
2343

    while ((next = readdir(dir)) != NULL) {
2344
2345
2346
	size_t dlen;

	/* Don't show the . entry. */
2347
	if (strcmp(next->d_name, ".") == 0)
2348
	   continue;
2349
2350
2351
2352
2353
	i++;

	dlen = strlenpt(next->d_name);
	if (dlen > *longest)
	    *longest = dlen;
2354
    }
2355
2356

    *numents = i;
2357
2358
2359
    rewinddir(dir);
    *longest += 10;

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

2362
2363
    path_len = strlen(path);

2364
2365
2366
2367
    i = 0;

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

2371
2372
	filelist[i] = charalloc(path_len + strlen(next->d_name) + 1);
	sprintf(filelist[i], "%s%s", path, next->d_name);
2373
2374
	i++;
    }
2375
2376
2377
2378
2379

    /* 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;
2380
    closedir(dir);
2381
2382
2383

    if (*longest > COLS - 1)
	*longest = COLS - 1;
2384
2385
    if (*longest < 7)
	*longest = 7;
2386
2387
2388
2389

    return filelist;
}

2390
2391
2392
/* 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
2393
{
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
    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;
2408
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2409

2410
    UNSET(CONSTUPDATE);
2411

2412
2413
2414
2415
2416
2417
2418
2419
  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
2420

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

2424
2425
2426
2427
    /* Get the list of files. */
    filelist = browser_init(path, &longest, &numents, dir);

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

2429
    /* Sort the list. */
Chris Allegretta's avatar
Chris Allegretta committed
2430
2431
2432
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
Chris Allegretta's avatar
Chris Allegretta committed
2433
2434

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

2448
	check_statusblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2449

2450
2451
	/* 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
2452
	fileline = selected;
2453
	if (width != 0)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2454
	    fileline /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2455
2456

	switch (kbinput) {
2457
#ifndef DISABLE_MOUSE
2458
2459
2460
	    case KEY_MOUSE:
		if (getmouse(&mevent) == ERR)
		    break;
2461

2462
2463
2464
2465
2466
2467
2468
2469
2470
		/* 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
2471
		    selected = (fileline / editwinrows) * editwinrows *
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
			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);
		}
2494

2495
		break;
2496
#endif
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
	    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
2516
		if (selected >= (editwinrows + fileline % editwinrows) *
2517
			width)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2518
		    selected -= (editwinrows + fileline % editwinrows) *
2519
2520
2521
2522
2523
2524
2525
			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
2526
		selected += (editwinrows - fileline % editwinrows) *
2527
2528
2529
2530
2531
2532
2533
			width;
		if (selected >= numents)
		    selected = numents - 1;
		break;
	    case NANO_HELP_KEY:
	    case NANO_HELP_FKEY:
	    case '?': /* Pico compatibility. */
2534
#ifndef DISABLE_HELP
2535
2536
		do_help();
		curs_set(0);
2537
#else
2538
		nano_disabled_msg();
2539
#endif
Rocco Corsi's avatar
   
Rocco Corsi committed
2540
		break;
2541
2542
2543
2544
2545
2546
2547
2548
2549
	    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
2550

2551
#ifndef DISABLE_OPERATINGDIR
2552
2553
2554
2555
2556
2557
		/* 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"),
2558
			operating_dir);
2559
2560
2561
		    beep();
		    break;
		}
2562
2563
#endif

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

2571
2572
2573
2574
2575
		if (!S_ISDIR(st.st_mode)) {
		    retval = mallocstrcpy(retval, filelist[selected]);
		    abort = TRUE;
		    break;
		}
2576

2577
2578
2579
2580
2581
2582
2583
		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
2584
		}
Chris Allegretta's avatar
Chris Allegretta committed
2585

2586
		path = mallocstrcpy(path, filelist[selected]);
2587

2588
2589
2590
2591
		/* Start over again with the new path value. */
		free_charptrarray(filelist, numents);
		goto change_browser_directory;

2592
2593
2594
2595
2596
	    /* Refresh the screen. */
	    case NANO_REFRESH_KEY:
		total_update();
		break;

2597
2598
2599
2600
2601
2602
2603
2604
	    /* 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
2605
#ifndef NANO_SMALL
2606
			NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2607
#endif
2608
			_("Go To Directory"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2609

2610
2611
		curs_set(0);
		bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2612

2613
2614
2615
2616
		if (j < 0) {
		    statusbar(_("Cancelled"));
		    break;
		}
Rocco Corsi's avatar
   
Rocco Corsi committed
2617

2618
2619
2620
2621
2622
2623
2624
		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
2625

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

2636
2637
2638
2639
2640
2641
2642
2643
2644
		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
2645

2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
		/* 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_CANCEL_KEY:
	    case NANO_EXIT_KEY:
	    case NANO_EXIT_FKEY:
	    case 'E': /* Pico compatibility. */
	    case 'e':
		abort = TRUE;
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2660
	}
2661

Chris Allegretta's avatar
Chris Allegretta committed
2662
2663
2664
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2665
2666
	blank_edit();

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

	wmove(edit, 0, 0);

2675
2676
2677
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
	{
	    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
2741
	    }
2742
2743

	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2744
	}
2745

2746
	wrefresh(edit);
2747
2748
    } while ((kbinput = get_kbinput(edit, &meta_key, &func_key)) !=
	NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
2749

Chris Allegretta's avatar
Chris Allegretta committed
2750
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2751
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2752
    edit_refresh();
2753
2754
2755
    curs_set(1);
    if (old_constupdate)
	SET(CONSTUPDATE);
Chris Allegretta's avatar
Chris Allegretta committed
2756

2757
    /* Clean up. */
Chris Allegretta's avatar
Chris Allegretta committed
2758
    free_charptrarray(filelist, numents);
2759
2760
    free(path);

Chris Allegretta's avatar
Chris Allegretta committed
2761
2762
    return retval;
}
2763

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2764
2765
2766
/* 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. */
2767
char *do_browse_from(const char *inpath)
2768
2769
{
    struct stat st;
2770
    char *path;
2771
	/* This holds the tilde-expanded version of inpath. */
2772
    DIR *dir = NULL;
2773

2774
2775
2776
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2777

2778
2779
2780
2781
2782
2783
    /* 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(). */
2784
2785
    if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	striponedir(path);
2786
2787
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2788

2789
2790
	    path = charalloc(PATH_MAX + 1);
	    path = getcwd(path, PATH_MAX + 1);
2791
2792
2793

	    if (path != NULL)
		align(&path);
2794
	}
2795
    }
2796

2797
#ifndef DISABLE_OPERATINGDIR
2798
2799
2800
2801
2802
2803
2804
    /* 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);
    }
2805
2806
#endif

2807
2808
2809
    if (path != NULL)
	dir = opendir(path);

2810
    if (dir == NULL) {
2811
	beep();
2812
2813
2814
2815
2816
	free(path);
	return NULL;
    }

    return do_browser(path, dir);
2817
}
Chris Allegretta's avatar
Chris Allegretta committed
2818
#endif /* !DISABLE_BROWSER */
2819

2820
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2821
2822
2823
/* 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)
2824
{
2825
    char *nanohist = NULL;
2826

2827
2828
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2829

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

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

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

2845
	if (hist == NULL) {
2846
	    if (errno != ENOENT) {
2847
2848
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2849
2850
2851
2852
		rcfile_error(N_("Error reading %s: %s"), nanohist,
			strerror(errno));
		fprintf(stderr,
			_("\nPress Return to continue starting nano\n"));
2853
2854
		while (getchar() != '\n')
		    ;
2855
	    }
2856
	} else {
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
	    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
2871
2872
2873
		    history = &replace_history;
	    }
	    fclose(hist);
2874
	    free(line);
2875
2876
	    UNSET(HISTORY_CHANGED);
	}
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
	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;
2893
    }
2894
    return TRUE;
2895
2896
2897
2898
2899
}

/* save histories to ~/.nano_history */
void save_history(void)
{
2900
    char *nanohist;
2901
2902

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

2907
2908
2909
2910
    nanohist = histfilename();

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

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

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