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

22
23
#include "config.h"

Chris Allegretta's avatar
Chris Allegretta committed
24
25
26
27
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
Chris Allegretta's avatar
Chris Allegretta committed
28
#include <sys/wait.h>
Chris Allegretta's avatar
Chris Allegretta committed
29
#include <sys/stat.h>
30
#include <utime.h>
Chris Allegretta's avatar
Chris Allegretta committed
31
32
#include <fcntl.h>
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
33
34
#include <ctype.h>
#include <dirent.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
/* Set a default value for PATH_MAX, so we can use it below in lines
 * like "path = getcwd(NULL, PATH_MAX + 1);". */
42
43
44
45
#ifndef PATH_MAX
#define PATH_MAX -1
#endif

Chris Allegretta's avatar
Chris Allegretta committed
46
47
48
/* What happens when there is no file to open? aiee! */
void new_file(void)
{
49
    fileage = make_new_node(NULL);
50
    fileage->data = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
51
    fileage->data[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
52
53
54
    filebot = fileage;
    edittop = fileage;
    current = fileage;
55
    current_x = 0;
Chris Allegretta's avatar
Chris Allegretta committed
56
    totlines = 1;
57
    totsize = 0;
58

59
60
#ifdef ENABLE_COLOR
    update_color();
61
62
    if (ISSET(COLOR_SYNTAX))
	edit_refresh();
63
#endif
Chris Allegretta's avatar
Chris Allegretta committed
64
65
}

66
67
68
69
70
71
/* 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
72
{
73
    filestruct *fileptr = (filestruct *)nmalloc(sizeof(filestruct));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
74

75
76
    /* Convert nulls to newlines.  len is the string's real length
     * here. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
77
78
    unsunder(buf, len);

79
80
81
    assert(strlen(buf) == len);

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

83
#ifndef NANO_SMALL
84
85
    /* If it's a DOS file (CR LF), and file conversion isn't disabled,
     * strip out the CR part. */
86
87
    if (!ISSET(NO_CONVERT) && len > 0 && buf[len - 1] == '\r') {
	fileptr->data[len - 1] = '\0';
88
	totsize--;
89
90
91
    }
#endif

92
93
94
    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
95
96
97
	fileptr->prev = NULL;
	fileptr->next = fileage;
	fileptr->lineno = 1;
98
99
	if (*first_line_ins) {
	    *first_line_ins = FALSE;
100
	    /* If we're inserting into the first line of the file, then
101
102
	     * we want to make sure that our edit buffer stays on the
	     * first line and that fileage stays up to date. */
103
104
105
	    edittop = fileptr;
	} else
	    filebot = fileptr;
Chris Allegretta's avatar
Chris Allegretta committed
106
	fileage = fileptr;
107
108
    } else {
	assert(prev != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
109
110
111
112
113
114
115
116
117
	fileptr->prev = prev;
	fileptr->next = NULL;
	fileptr->lineno = prev->lineno + 1;
	prev->next = fileptr;
    }

    return fileptr;
}

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

159
    buf = charalloc(bufx);
160
    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
161

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

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

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

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

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

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

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

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

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

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

214
215
216
	    /* 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. */
217
	    len = 1;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
218

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

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

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

251
#ifndef NANO_SMALL
252
253
254
    /* If file conversion isn't disabled and the last character in this
     * file is a CR, read it in properly as a Mac format line. */
    if (len == 0 && !ISSET(NO_CONVERT) && input == '\r') {
255
256
257
	buf[0] = input;
	buf[1] = '\0';
	len = 1;
258
259
    }
#endif
260

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

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

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

285
    /* Did we try to insert a file of 0 bytes? */
286
287
288
289
290
291
292
293
294
295
296
297
    if (num_lines != 0) {
	if (current != NULL) {
	    fileptr->next = current;
	    current->prev = fileptr;
	    renumber(current);
	    current_x = 0;
	    placewewant = 0;
	} else if (fileptr->next == NULL) {
	    filebot = fileptr;
	    new_magicline();
	    totsize--;
	}
Chris Allegretta's avatar
Chris Allegretta committed
298
    }
299
300

#ifndef NANO_SMALL
301
302
303
304
305
306
307
308
309
    if (fileformat == 3)
	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);
    else if (fileformat == 2)
	statusbar(P_("Read %lu line (Converted from Mac format)",
		"Read %lu lines (Converted from Mac format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
310
    else if (fileformat == 1)
311
312
313
	statusbar(P_("Read %lu line (Converted from DOS format)",
		"Read %lu lines (Converted from DOS format)",
		(unsigned long)num_lines), (unsigned long)num_lines);
314
315
    else
#endif
316
317
	statusbar(P_("Read %lu line", "Read %lu lines",
		(unsigned long) num_lines),(unsigned long)num_lines);
318

319
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
320
321
}

322
323
324
325
326
327
328
329
/* 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
330
331
332
333
{
    int fd;
    struct stat fileinfo;

334
335
336
337
    assert(f != NULL);
    if (filename == NULL || filename[0] == '\0' ||
	    stat(filename, &fileinfo) == -1) {
	if (newfie) {
Chris Allegretta's avatar
Chris Allegretta committed
338
	    statusbar(_("New File"));
339
	    return -2;
Chris Allegretta's avatar
Chris Allegretta committed
340
	}
341
342
	statusbar(_("\"%s\" not found"), filename);
	return -1;
343
344
345
346
347
    } else if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) ||
		S_ISBLK(fileinfo.st_mode)) {
	/* Don't open character or block files.  Sorry, /dev/sndstat! */
	statusbar(S_ISDIR(fileinfo.st_mode) ? _("\"%s\" is a directory") :
			_("File \"%s\" is a device file"), filename);
348
	return -1;
Chris Allegretta's avatar
Chris Allegretta committed
349
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
350
351
352
353
354
355
356
357
	statusbar(_("Error reading %s: %s"), filename, strerror(errno));
 	return -1;
     } else {
	/* File is A-OK. */
	*f = fdopen(fd, "rb"); /* Binary for our own line-end munging */

	if (*f == NULL) {
	    statusbar(_("Error reading %s: %s"), filename, strerror(errno));
358
	    close(fd);
359
360
	} else
	    statusbar(_("Reading File"));
Chris Allegretta's avatar
Chris Allegretta committed
361
    }
362
    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
363
364
}

365
/* This function will return the name of the first available extension
366
367
 * of a filename (starting with filename.save, then filename.save.1,
 * etc.).  Memory is allocated for the return value.  If no writable
368
 * extension exists, we return "". */
Chris Allegretta's avatar
Chris Allegretta committed
369
char *get_next_filename(const char *name)
370
371
{
    int i = 0;
372
373
    char *buf;
    size_t namelen = strlen(name);
374

375
    buf = charalloc(namelen + num_of_digits(INT_MAX) + 7);
376
    strcpy(buf, name);
377
378
    strcpy(buf + namelen, ".save");
    namelen += 5;
379

380
    while (TRUE) {
381
	struct stat fs;
382
383

	if (stat(buf, &fs) == -1)
Chris Allegretta's avatar
Chris Allegretta committed
384
	    return buf;
385
386
387
388
	if (i == INT_MAX)
	    break;

	i++;
389
	sprintf(buf + namelen, ".%d", i);
390
391
    }

Chris Allegretta's avatar
Chris Allegretta committed
392
    /* We get here only if there is no possible save file. */
393
    null_at(&buf, 0);
394
395
396
    return buf;
}

397
398
#ifndef NANO_SMALL
void execute_command(const char *command)
Chris Allegretta's avatar
Chris Allegretta committed
399
{
400
401
402
403
404
405
406
#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);
407
    }
408
409
410
411
412
413
414
415
416
#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
417

418
419
420
421
422
423
424
/* 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)
{
    bool new_buffer = fileage == NULL
#ifdef ENABLE_MULTIBUFFER
	 || ISSET(MULTIBUFFER)
425
#endif
426
427
428
429
430
431
432
433
434
	;
	/* new_buffer says whether we load to 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. */
    FILE *f;
    int rc;
	/* rc == -2 means that the statusbar displayed "New File".  -1
	 * means that the open failed.  0 means success. */
435

436
437
438
439
440
#ifndef DISABLE_OPERATINGDIR
    if (check_operating_dir(name, FALSE)) {
	statusbar(_("Can't insert file from outside of %s"), operating_dir);
	return;
    }
441
442
#endif

443
444
445
#ifdef ENABLE_MULTIBUFFER
    /* Update the current entry in the open_files structure. */
    add_open_file(TRUE);
446
447
#endif

448
449
450
451
452
    rc = open_file(name, new_buffer, &f);

#ifdef ENABLE_MULTIBUFFER
    if (rc != -1 && ISSET(MULTIBUFFER)) {
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
453
#ifndef NANO_SMALL
454
	UNSET(MARK_ISSET);
Chris Allegretta's avatar
Chris Allegretta committed
455
#endif
456
    }
457
#endif
458
459
460
461
462
463
464
465

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

    if (rc == 0) {
	read_file(f, filename);
Chris Allegretta's avatar
Chris Allegretta committed
466
#ifndef NANO_SMALL
467
	stat(filename, &originalfilestat);
Chris Allegretta's avatar
Chris Allegretta committed
468
#endif
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
    }

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

void do_insertfile(void)
{
    int i;
    const char *msg;
    char *inspath = mallocstrcpy(NULL, "");
	/* The last answer the user typed on the statusbar.  Saved for if
	 * they do M-F or cancel the file browser. */
484

485
486
487
488
489
490
491
492
493
    wrap_reset();

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

#ifdef ENABLE_MULTIBUFFER
    if (ISSET(MULTIBUFFER))
	msg = N_("File to insert into new buffer [from %s] ");
494
    else
495
#endif
496
497
	msg = N_("File to insert [from %s] ");
    i = statusq(TRUE, insertfile_list, inspath,
Chris Allegretta's avatar
Chris Allegretta committed
498
#ifndef NANO_SMALL
499
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
500
#endif
501
502
503
504
		_(msg),
#ifndef DISABLE_OPERATINGDIR
		operating_dir != NULL && strcmp(operating_dir, ".") != 0 ?
		operating_dir :
505
#endif
506
		"./");
507

Chris Allegretta's avatar
Chris Allegretta committed
508
    if (i != -1) {
509
510
	int old_current_x = current_x;

511
	inspath = mallocstrcpy(inspath, answer);
Chris Allegretta's avatar
Chris Allegretta committed
512

513
#ifndef NANO_SMALL
514
#ifdef ENABLE_MULTIBUFFER
515
	if (i == TOGGLE_MULTIBUFFER_KEY) {
516
517
	    /* Don't allow toggling if we're in view mode. */
	    if (!ISSET(VIEW_MODE))
518
		TOGGLE(MULTIBUFFER);
519
	    goto start_again;
520
	}
521
#endif /* ENABLE_MULTIBUFFER */
522
	if (i == NANO_EXTCMD_KEY) {
523
524
	    char *ans = mallocstrcpy(NULL, answer);
	    int ts = statusq(TRUE, extcmd_list, ans, NULL, 
525
		_("Command to execute"));
526
527
528

	    free(ans);

529
530
	    if (ts == -1 || answer == NULL || answer[0] == '\0')
		goto start_again;
531
	}
532
#endif /* !NANO_SMALL */
533
534
535
536
#ifndef DISABLE_BROWSER
	if (i == NANO_TOFILES_KEY) {
	    char *tmp = do_browse_from(answer);

537
	    if (tmp == NULL)
538
		goto start_again;
539
540
541
	    resetstatuspos = TRUE;
	    free(answer);
	    answer = tmp;
Chris Allegretta's avatar
Chris Allegretta committed
542
543
544
	}
#endif

545
#ifndef NANO_SMALL
546
547
548
	if (i == NANO_EXTCMD_KEY)
	    execute_command(answer);
	else {
549
#endif
550
551
	    answer = mallocstrassn(answer, real_dir_from_tilde(answer));
	    load_buffer(answer);
552
#ifndef NANO_SMALL
553
	}
554
#endif
555

556
#ifdef ENABLE_MULTIBUFFER
557
558
559
	if (ISSET(MULTIBUFFER)) {
	    /* Update the titlebar. */
	    titlebar(NULL);
560

561
562
563
	    /* Reinitialize the shortcut list. */
	    shortcut_init(FALSE);
	} else {
564
#endif
565
	    /* Mark the file as modified. */
566
	    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
567

568
569
	    /* Restore the old x-coordinate position. */
	    current_x = old_current_x;
570
#ifdef ENABLE_MULTIBUFFER
571
	}
Chris Allegretta's avatar
Chris Allegretta committed
572
573
#endif

574
	/* If we've gone off the bottom, recenter; otherwise, just redraw */
575
	edit_refresh();
576
    } else
Chris Allegretta's avatar
Chris Allegretta committed
577
	statusbar(_("Cancelled"));
Chris Allegretta's avatar
Chris Allegretta committed
578

579
    free(inspath);
Chris Allegretta's avatar
Chris Allegretta committed
580
581
}

582
void do_insertfile_void(void)
583
{
584
#ifdef ENABLE_MULTIBUFFER
585
586
    if (ISSET(VIEW_MODE) && !ISSET(MULTIBUFFER))
	statusbar(_("Key illegal in non-multibuffer mode"));
587
    else
588
#endif
589
	do_insertfile();
590
591
592
593

    display_main_list();
}

594
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
595
596
597
/* Create a new openfilestruct node. */
openfilestruct *make_new_opennode(openfilestruct *prevnode)
{
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
598
    openfilestruct *newnode = (openfilestruct *)nmalloc(sizeof(openfilestruct));
Chris Allegretta's avatar
Chris Allegretta committed
599
600
601
602
603
604
605
606
607
608
609
610
611

    newnode->filename = NULL;
    newnode->fileage = NULL;
    newnode->filebot = NULL;

    newnode->prev = prevnode;
    newnode->next = NULL;

    return newnode;
}

/* Splice a node into an existing openfilestruct. */
void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
612
	openfilestruct *end)
Chris Allegretta's avatar
Chris Allegretta committed
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
{
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

/* Unlink a node from the rest of the openfilestruct. */
void unlink_opennode(const openfilestruct *fileptr)
{
    assert(fileptr != NULL);

    if (fileptr->prev != NULL)
	fileptr->prev->next = fileptr->next;

    if (fileptr->next != NULL)
	fileptr->next->prev = fileptr->prev;
}

/* Delete a node from the openfilestruct. */
void delete_opennode(openfilestruct *fileptr)
{
    if (fileptr != NULL) {
	if (fileptr->filename != NULL)
	    free(fileptr->filename);
	if (fileptr->fileage != NULL)
	    free_filestruct(fileptr->fileage);
	free(fileptr);
    }
}

/* Deallocate all memory associated with this and later files,
 * including the lines of text. */
void free_openfilestruct(openfilestruct *src)
{
    if (src != NULL) {
	while (src->next != NULL) {
	    src = src->next;
	    delete_opennode(src->prev);
#ifdef DEBUG
654
	    fprintf(stderr, "%s: free'd a node, YAY!\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
655
656
657
658
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
659
	fprintf(stderr, "%s: free'd last node.\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
660
661
662
663
#endif
    }
}

664
/*
665
 * Add/update an entry to the open_files openfilestruct.  If update is
666
667
 * FALSE, a new entry is created; otherwise, the current entry is
 * updated.
668
 */
669
void add_open_file(int update)
670
{
671
    openfilestruct *tmp;
672

673
    if (fileage == NULL || current == NULL || filename == NULL)
674
	return;
675
676

    /* if no entries, make the first one */
677
    if (open_files == NULL)
678
	open_files = make_new_opennode(NULL);
679
680
681
682
683
684
685

    else if (!update) {

	/* otherwise, if we're not updating, make a new entry for
	   open_files and splice it in after the current one */

#ifdef DEBUG
686
	fprintf(stderr, "filename is %s\n", open_files->filename);
687
688
#endif

689
690
	tmp = make_new_opennode(NULL);
	splice_opennode(open_files, tmp, open_files->next);
691
692
693
694
	open_files = open_files->next;
    }

    /* save current filename */
695
    open_files->filename = mallocstrcpy(open_files->filename, filename);
696

697
698
699
700
701
#ifndef NANO_SMALL
    /* save the file's stat */
    open_files->originalfilestat = originalfilestat;
#endif

702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
    /* save current total number of lines */
    open_files->file_totlines = totlines;

    /* save current total size */
    open_files->file_totsize = totsize;

    /* save current x-coordinate position */
    open_files->file_current_x = current_x;

    /* save current y-coordinate position */
    open_files->file_current_y = current_y;

    /* save current place we want */
    open_files->file_placewewant = placewewant;

    /* save current line number */
718
    open_files->file_lineno = current->lineno;
719

720
721
722
723
    /* start with default modification status: unmodified (and marking
       status, if available: unmarked) */
    open_files->file_flags = 0;

Chris Allegretta's avatar
Chris Allegretta committed
724
725
726
    /* if we're updating, save current modification status (and marking
       status, if available) */
    if (update) {
727
728
	if (ISSET(MODIFIED))
	    open_files->file_flags |= MODIFIED;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
729
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
730
731
732
	if (ISSET(MARK_ISSET)) {
	    open_files->file_mark_beginbuf = mark_beginbuf;
	    open_files->file_mark_beginx = mark_beginx;
733
	    open_files->file_flags |= MARK_ISSET;
Chris Allegretta's avatar
Chris Allegretta committed
734
735
736
737
	}
#endif
    }

738
739
740
    /* if we're not in view mode and not updating, the file contents
       might have changed, so save the filestruct; otherwise, don't */
    if (!(ISSET(VIEW_MODE) && !update)) {
Chris Allegretta's avatar
Chris Allegretta committed
741
742
743
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
744
    }
745
746

#ifdef DEBUG
747
    fprintf(stderr, "filename is %s\n", open_files->filename);
748
749
750
751
752
#endif
}

/*
 * Read the current entry in the open_files structure and set up the
753
 * currently open file using that entry's information.
754
 */
755
void load_open_file(void)
756
{
757
    if (open_files == NULL)
758
	return;
759
760
761

    /* set up the filename, the file buffer, the total number of lines in
       the file, and the total file size */
762
    filename = mallocstrcpy(filename, open_files->filename);
763
764
765
#ifndef NANO_SMALL
    originalfilestat = open_files->originalfilestat;
#endif
766
    fileage = open_files->fileage;
767
    current = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
768
    filebot = open_files->filebot;
769
770
771
    totlines = open_files->file_totlines;
    totsize = open_files->file_totsize;

Chris Allegretta's avatar
Chris Allegretta committed
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
    /* restore modification status */
    if (open_files->file_flags & MODIFIED)
	SET(MODIFIED);
    else
	UNSET(MODIFIED);

#ifndef NANO_SMALL
    /* restore marking status */
    if (open_files->file_flags & MARK_ISSET) {
	mark_beginbuf = open_files->file_mark_beginbuf;
	mark_beginx = open_files->file_mark_beginx;
	SET(MARK_ISSET);
    } else
	UNSET(MARK_ISSET);
#endif
787

Chris Allegretta's avatar
Chris Allegretta committed
788
789
790
791
#ifdef ENABLE_COLOR
    update_color();
#endif

792
793
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
794
795
    do_gotopos(open_files->file_lineno, open_files->file_current_x,
	open_files->file_current_y, open_files->file_placewewant);
796

Chris Allegretta's avatar
Chris Allegretta committed
797
    /* update the titlebar */
798
799
800
801
802
803
    clearok(topwin, FALSE);
    titlebar(NULL);
}

/*
 * Open the previous entry in the open_files structure.  If closing_file
804
805
806
 * is FALSE, update the current entry before switching from it.
 * Otherwise, we are about to close that entry, so don't bother doing
 * so.
807
 */
808
void open_prevfile(int closing_file)
809
{
810
    if (open_files == NULL)
811
	return;
812
813

    /* if we're not about to close the current entry, update it before
814
       doing anything */
815
    if (!closing_file)
816
	add_open_file(TRUE);
817

818
    if (open_files->prev == NULL && open_files->next == NULL) {
819
820
821

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
822
	    statusbar(_("No more open file buffers"));
823
	return;
824
825
    }

826
    if (open_files->prev != NULL) {
827
828
829
	open_files = open_files->prev;

#ifdef DEBUG
830
	fprintf(stderr, "filename is %s\n", open_files->filename);
831
832
833
834
#endif

    }

835
    else if (open_files->next != NULL) {
836
837

	/* if we're at the beginning, wrap around to the end */
838
	while (open_files->next != NULL)
839
840
841
	    open_files = open_files->next;

#ifdef DEBUG
842
	    fprintf(stderr, "filename is %s\n", open_files->filename);
843
844
845
846
847
848
#endif

    }

    load_open_file();

849
    statusbar(_("Switched to %s"),
850
851
      ((open_files->filename[0] == '\0') ? "New Buffer" :
	open_files->filename));
852

853
854
855
856
857
#ifdef DEBUG
    dump_buffer(current);
#endif
}

858
void open_prevfile_void(void)
859
{
860
    open_prevfile(FALSE);
861
862
}

863
864
/*
 * Open the next entry in the open_files structure.  If closing_file is
865
866
 * FALSE, update the current entry before switching from it.  Otherwise,
 * we are about to close that entry, so don't bother doing so.
867
 */
868
void open_nextfile(int closing_file)
869
{
870
    if (open_files == NULL)
871
	return;
872
873

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

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

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

886
    if (open_files->next != NULL) {
887
888
889
	open_files = open_files->next;

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

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

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

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

	}
    }

    load_open_file();

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

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

918
void open_nextfile_void(void)
919
{
920
    open_nextfile(FALSE);
921
922
}

923
924
/*
 * Delete an entry from the open_files filestruct.  After deletion of an
925
 * entry, the next or previous entry is opened, whichever is found first.
926
927
928
929
 * Return 0 on success or 1 on error.
 */
int close_open_file(void)
{
930
    openfilestruct *tmp;
931

932
    if (open_files == NULL)
933
934
	return 1;

Chris Allegretta's avatar
Chris Allegretta committed
935
936
937
    /* make sure open_files->fileage and fileage, and open_files->filebot
       and filebot, are in sync; they might not be if lines have been cut
       from the top or bottom of the file */
938
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
939
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
940

941
    tmp = open_files;
942
943
944
945
946
947
    if (open_files->next != NULL)
	open_nextfile(TRUE);
    else if (open_files->prev != NULL)
	open_prevfile(TRUE);
    else
	return 1;
948

949
950
    unlink_opennode(tmp);
    delete_opennode(tmp);
951

952
    shortcut_init(FALSE);
953
954
955
    display_main_list();
    return 0;
}
956
#endif /* ENABLE_MULTIBUFFER */
957

958
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR) || !defined(NANO_SMALL)
959
/*
960
961
962
963
964
965
 * When passed "[relative path]" or "[relative path][filename]" in
 * origpath, return "[full path]" or "[full path][filename]" on success,
 * or NULL on error.  This is still done if the file doesn't exist but
 * the relative path does (since the file could exist in memory but not
 * yet on disk); it is not done if the relative path doesn't exist (since
 * the first call to chdir() will fail then).
966
 */
967
char *get_full_path(const char *origpath)
968
{
969
970
971
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
972
    char *expanded_origpath;
973

974
    /* first, get the current directory, and tack a slash onto the end of
975
       it, unless it turns out to be "/", in which case leave it alone */
976
977
978

    d_here = getcwd(NULL, PATH_MAX + 1);

979
    if (d_here != NULL) {
980
981

	align(&d_here);
982
	if (strcmp(d_here, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
983
	    d_here = charealloc(d_here, strlen(d_here) + 2);
984
985
	    strcat(d_here, "/");
	}
986
987
988
989
990

	/* stat origpath; if stat() fails, assume that origpath refers to
	   a new file that hasn't been saved to disk yet (i. e. set
	   path_only to 0); if stat() succeeds, set path_only to 0 if
	   origpath doesn't refer to a directory, or to 1 if it does */
991
	path_only = !stat(origpath, &fileinfo) && S_ISDIR(fileinfo.st_mode);
992

993
	expanded_origpath = real_dir_from_tilde(origpath);
994
	/* save the value of origpath in both d_there and d_there_file */
995
996
997
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
998

999
1000
1001
1002
1003
1004
	/* if we have a path but no filename, tack slashes onto the ends
	   of both d_there and d_there_file, if they don't end in slashes
	   already */
	if (path_only) {
	    tmp = d_there[strlen(d_there) - 1];
	    if (tmp != '/') {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1005
		d_there = charealloc(d_there, strlen(d_there) + 2);
1006
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1007
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1008
1009
1010
1011
		strcat(d_there_file, "/");
	    }
	}

1012
1013
1014
1015
1016
	/* search for the last slash in d_there */
	last_slash = strrchr(d_there, '/');

	/* if we didn't find one, copy d_here into d_there; all data is
	   then set up */
1017
	if (last_slash == NULL)
1018
	    d_there = mallocstrcpy(d_there, d_here);
1019
	else {
1020
1021
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1022
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1023
	    null_at(&d_there, last_slash_index + 1);
1024
1025
1026

	    /* and remove all non-file elements from d_there_file (i. e.
	       everything before and including the last slash); if we
1027
	       have a path but no filename, don't do anything */
1028
1029
1030
1031
1032
1033
	    if (!path_only) {
		last_slash = strrchr(d_there_file, '/');
		last_slash++;
		strcpy(d_there_file, last_slash);
		align(&d_there_file);
	    }
1034
1035
1036

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1037
1038
1039
		/* get the full pathname, and save it back in d_there,
		   tacking a slash on the end if we have a path but no
		   filename; if the saving fails, get out */
1040
1041
1042
1043
1044
1045

		free(d_there);

		d_there = getcwd(NULL, PATH_MAX + 1);

		align(&d_there);
1046
		if (d_there != NULL) {
1047
1048
1049

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
1050
		    if (strcmp(d_there, "/") != 0) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1051
			d_there = charealloc(d_there, strlen(d_there) + 2);
1052
1053
			strcat(d_there, "/");
		    }
1054
1055
1056
		}
		else
		    return NULL;
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
	    }

	    /* finally, go back to where we were before, d_here (no error
	       checking is done on this chdir(), because we can do
	       nothing if it fails) */
	    chdir(d_here);
	}
	
	/* all data is set up; fill in newpath */

1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
	/* if we have a path and a filename, newpath = d_there +
	   d_there_file; otherwise, newpath = d_there */
	if (!path_only) {
	    newpath = charalloc(strlen(d_there) + strlen(d_there_file) + 1);
	    strcpy(newpath, d_there);
	    strcat(newpath, d_there_file);
	}
	else {
	    newpath = charalloc(strlen(d_there) + 1);
	    strcpy(newpath, d_there);
	}
1078
1079
1080
1081
1082
1083
1084
1085
1086

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

    return newpath;
}
1087
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1088
1089
1090

#ifndef DISABLE_SPELLER
/*
1091
1092
1093
 * This function accepts a path and returns the full path (via
 * get_full_path()).  On error, if the path doesn't reference a
 * directory, or if the directory isn't writable, it returns NULL.
1094
 */
1095
char *check_writable_directory(const char *path)
1096
{
1097
    char *full_path = get_full_path(path);
1098
    int writable;
1099
1100
    struct stat fileinfo;

1101
    /* if get_full_path() failed, return NULL */
1102
    if (full_path == NULL)
1103
	return NULL;
1104
1105
1106

    /* otherwise, stat() the full path to see if it's writable by the
       user; set writable to 1 if it is, or 0 if it isn't */
1107
    writable = !stat(full_path, &fileinfo) && (fileinfo.st_mode & S_IWUSR);
1108
1109

    /* if the full path doesn't end in a slash (meaning get_full_path()
1110
1111
1112
1113
       found that it isn't a directory) or isn't writable, free full_path
       and return NULL */
    if (full_path[strlen(full_path) - 1] != '/' || writable == 0) {
	free(full_path);
1114
	return NULL;
1115
    }
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125

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

/*
 * This function accepts a directory name and filename prefix the same
 * way that tempnam() does, determines the location for its temporary
 * file the same way that tempnam() does, safely creates the temporary
 * file there via mkstemp(), and returns the name of the temporary file
1126
1127
1128
1129
1130
1131
1132
 * the same way that tempnam() does.  It does not reference the value of
 * TMP_MAX because the total number of random filenames that it can
 * generate using one prefix is equal to 256**6, which is a sufficiently
 * large number to handle most cases.  Since the behavior after tempnam()
 * generates TMP_MAX random filenames is implementation-defined, my
 * implementation is to go on generating random filenames regardless of
 * it.
1133
 */
1134
1135
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1136
1137
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1138
    int filedesc;
1139

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

1147
1148
1149
    /* if $TMPDIR is blank or isn't set, or isn't a writable
       directory, and dirname isn't NULL, try it; otherwise, leave
       full_tempdir set to NULL */
1150
    if (full_tempdir == NULL && dirname != NULL)
1151
	full_tempdir = check_writable_directory(dirname);
1152
1153
1154

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1155
    if (full_tempdir == NULL)
1156
	full_tempdir = check_writable_directory(P_tmpdir);
1157
1158

    /* if P_tmpdir didn't work, use /tmp instead */
1159
    if (full_tempdir == NULL) {
1160
1161
1162
1163
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1164
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1165
1166

    /* like tempnam(), use only the first 5 characters of the prefix */
1167
1168
1169
1170
1171
1172
1173
1174
    strncat(full_tempdir, filename_prefix, 5);
    strcat(full_tempdir, "XXXXXX");
    filedesc = mkstemp(full_tempdir);

    /* if mkstemp succeeded, close the resulting file; afterwards, it'll be
       0 bytes long, so delete it; finally, return the filename (all that's
       left of it) */
    if (filedesc != -1) {
1175
	close(filedesc);
1176
1177
	unlink(full_tempdir);
	return full_tempdir;
1178
    }
1179
1180
1181

    free(full_tempdir);
    return NULL;
1182
1183
}
#endif /* !DISABLE_SPELLER */
1184
1185

#ifndef DISABLE_OPERATINGDIR
1186
1187
1188
1189
1190
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1191
    if (operating_dir == NULL)
1192
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1193

1194
1195
1196
    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
1197
     * inaccessible, unset operating_dir. */
1198
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1199
1200
1201
1202
1203
1204
1205
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1206
/* Check to see if we're inside the operating directory.  Return 0 if we
1207
1208
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1209
 * completion will work. */
1210
int check_operating_dir(const char *currpath, int allow_tabcomp)
1211
{
1212
1213
1214
1215
    /* 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. */
1216

1217
1218
1219
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1220

1221
    /* If no operating directory is set, don't bother doing anything. */
1222
    if (operating_dir == NULL)
1223
	return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1224

1225
    assert(full_operating_dir != NULL);
1226
1227

    fullpath = get_full_path(currpath);
1228
1229
1230
1231
1232
1233
1234
1235

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

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

1243
1244
1245
1246
1247
    /* 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. */
1248
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1249
1250
	retval = 1;
    free(fullpath);	
1251
1252

    /* Otherwise, we're still inside it. */
1253
    return retval;
1254
}
1255
1256
#endif

1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
#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

1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
/* 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;
}

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

1353
    assert(name != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1354
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1355
1356
1357
	statusbar(_("Cancelled"));
	return -1;
    }
1358
1359
    if (!tmp)
	titlebar(NULL);
1360

1361
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1362

1363
#ifndef DISABLE_OPERATINGDIR
1364
    /* If we're writing a temporary file, we're probably going outside
1365
     * the operating directory, so skip the operating directory test. */
1366
    if (!tmp && check_operating_dir(realname, FALSE) != 0) {
1367
1368
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1369
1370
1371
    }
#endif

1372
1373
1374
1375
1376
1377
1378
    anyexists = lstat(realname, &lst) != -1;
    /* New case: if the file exists, just give up. */
    if (tmp && anyexists)
	goto cleanup_and_exit;
    /* If NOFOLLOW_SYMLINKS is set, it doesn't make sense to prepend or
     * append to a symlink.  Here we warn about the contradiction. */
    if (ISSET(NOFOLLOW_SYMLINKS) && anyexists && S_ISLNK(lst.st_mode)) {
1379
1380
	statusbar(
		_("Cannot prepend or append to a symlink with --nofollow set"));
1381
1382
1383
	goto cleanup_and_exit;
    }

1384
    /* Save the state of file at the end of the symlink (if there is
1385
1386
     * one). */
    realexists = stat(realname, &st) != -1;
1387

1388
1389
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1390
1391
1392
1393
1394
1395
1396
1397
     * temporary, and the file already exists.  Furthermore, if we
     * aren't appending, prepending, or writing a selection, we backup
     * only if the file has not been modified by someone else since nano
     * opened it. */
    if (ISSET(BACKUP_FILE) && !tmp && realexists != 0 &&
	(append != 0 || ISSET(MARK_ISSET) ||
	originalfilestat.st_mtime == st.st_mtime)) {

1398
	FILE *backup_file;
1399
	char *backupname;
1400
	struct utimbuf filetime;
1401
	int copy_status;
1402

1403
	/* Save the original file's access and modification times. */
1404
1405
1406
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1407
	/* Open the original file to copy to the backup. */
1408
	f = fopen(realname, "rb");
1409
	if (f == NULL) {
1410
	    statusbar(_("Error reading %s: %s"), realname,
1411
		strerror(errno));
1412
	    goto cleanup_and_exit;
1413
1414
	}

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

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

#ifdef DEBUG
1463
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1464
1465
#endif

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

1484
1485
1486
1487
1488
1489
    /* 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));
1490
	goto cleanup_and_exit;
1491
    }
1492

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

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

1501
    /* If we're prepending, copy the file to a temp file. */
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
    if (append == 2) {
	int fd_source;
	FILE *f_source = NULL;

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

1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
	fd_source = open(realname, O_RDONLY | O_CREAT);
	if (fd_source != -1) {
	    f_source = fdopen(fd_source, "rb");
	    if (f_source == NULL)
		close(fd_source);
	}
	if (f_source == NULL) {
	    statusbar(_("Error reading %s: %s"), realname, strerror(errno));
	    fclose(f);
	    unlink(tempname);
	    goto cleanup_and_exit;
	}

	if (copy_file(f_source, f) != 0) {
	    statusbar(_("Error writing %s: %s"), tempname, strerror(errno));
	    unlink(tempname);
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
1554
1555
1556
    umask(original_umask);

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

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

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

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

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

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

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

	if (!ISSET(MAC_FILE))
1594
#endif
1595
1596
1597
1598
1599
	    if (putc('\n', f) == EOF) {
		statusbar(_("Error writing %s: %s"), realname, strerror(errno));
		fclose(f);
		goto cleanup_and_exit;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1600
1601
1602
1603
1604

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

1605
    /* If we're prepending, open the temp file, and append it to f. */
1606
    if (append == 2) {
1607
1608
1609
1610
1611
1612
1613
1614
	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);
1615
	}
1616
	if (f_source == NULL) {
1617
1618
	    statusbar(_("Error reading %s: %s"), tempname, strerror(errno));
	    fclose(f);
1619
	    goto cleanup_and_exit;
1620
1621
	}

1622
1623
1624
	if (copy_file(f_source, f) == -1
		|| unlink(tempname) == -1) {
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1625
	    goto cleanup_and_exit;
1626
	}
1627
1628
1629
1630
    } 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
1631
    }
1632

1633
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1634
	if (!nonamechange) {
1635
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1636
1637
#ifdef ENABLE_COLOR
	    update_color();
1638
1639
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1640
1641
#endif
	}
1642

1643
1644
1645
1646
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
1647
1648
	statusbar(P_("Wrote %u line", "Wrote %u lines", lineswritten),
		lineswritten);
1649
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1650
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1651
    }
1652
1653
1654
1655
1656

    retval = 1;

  cleanup_and_exit:
    free(realname);
1657
    free(tempname);
1658
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1659
1660
}

1661
1662
1663
#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
1664
1665
1666
1667
1668
 * 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. */
int write_marked(const char *name, int tmp, int append)
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
{
    int retval = -1;
    filestruct *fileagebak = fileage;
    filestruct *filebotbak = filebot;
    int oldmod = ISSET(MODIFIED);
	/* write_file() unsets the MODIFIED flag. */
    size_t topx;
	/* The column of the beginning of the mark. */
    char origchar;
	/* We replace the character at the end of the mark with '\0'.
	 * We save the original character, to restore it. */
    char *origcharloc;
	/* The location of the character we nulled. */

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

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

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

1710
    retval = write_file(name, tmp, append, TRUE);
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723

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

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

1724
int do_writeout(int exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1725
{
1726
1727
    int i;
    int append = 0;
1728
#ifdef NANO_EXTRA
1729
    static int did_cred = FALSE;
1730
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1731

1732
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1733
    currshortcut = writefile_list;
1734
1735
#endif

1736
    if (exiting && filename[0] != '\0' && ISSET(TEMP_FILE)) {
1737
1738
1739
1740
1741
	i = write_file(filename, FALSE, 0, FALSE);
	if (i == 1) {
	    /* Write succeeded. */
	    display_main_list();
	    return 1;
1742
	}
Chris Allegretta's avatar
Chris Allegretta committed
1743
1744
    }

1745
#ifndef NANO_SMALL
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
    if (ISSET(MARK_ISSET) && !exiting)
	answer = mallocstrcpy(answer, "");
    else
#endif
	answer = mallocstrcpy(answer, filename);

    while (TRUE) {
	const char *msg;
#ifndef NANO_SMALL
	char *ans = mallocstrcpy(NULL, answer);
1756
1757
	const char *formatstr, *backupstr;

1758
	if (ISSET(MAC_FILE))
1759
	   formatstr = N_(" [Mac Format]");
1760
	else if (ISSET(DOS_FILE))
1761
	   formatstr = N_(" [DOS Format]");
1762
1763
1764
	else
	   formatstr = "";

1765
	if (ISSET(BACKUP_FILE))
1766
	   backupstr = N_(" [Backup]");
1767
1768
1769
	else
	   backupstr = "";

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

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

#ifndef NANO_SMALL
	free(ans);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1802

1803
1804
1805
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
1806
	    return -1;
1807
	}
Chris Allegretta's avatar
Chris Allegretta committed
1808

1809
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1810
	if (i == NANO_TOFILES_KEY) {
1811
	    char *tmp = do_browse_from(answer);
1812

1813
	    currshortcut = writefile_list;
1814
1815
1816
1817
	    if (tmp == NULL)
		continue;
	    free(answer);
	    answer = tmp;
1818
	} else
1819
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1820
#ifndef NANO_SMALL
1821
1822
1823
	if (i == TOGGLE_DOS_KEY) {
	    UNSET(MAC_FILE);
	    TOGGLE(DOS_FILE);
1824
	    continue;
1825
1826
1827
	} else if (i == TOGGLE_MAC_KEY) {
	    UNSET(DOS_FILE);
	    TOGGLE(MAC_FILE);
1828
	    continue;
1829
1830
	} else if (i == TOGGLE_BACKUP_KEY) {
	    TOGGLE(BACKUP_FILE);
1831
1832
	    continue;
	} else
1833
#endif /* !NANO_SMALL */
1834
1835
1836
1837
1838
1839
1840
	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
1841

Chris Allegretta's avatar
Chris Allegretta committed
1842
#ifdef DEBUG
1843
	fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1844
#endif
1845
1846

#ifdef NANO_EXTRA
1847
	if (exiting && !ISSET(TEMP_FILE) && strcasecmp(answer, "zzy") == 0
1848
		&& !did_cred) {
1849
	    do_credits();
1850
	    did_cred = TRUE;
1851
1852
	    return -1;
	}
1853
#endif
1854
	if (append == 0 && strcmp(answer, filename) != 0) {
1855
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1856

1857
	    if (!stat(answer, &st)) {
1858
		i = do_yesno(FALSE, _("File exists, OVERWRITE ? "));
1859
1860
		if (i == 0 || i == -1)
		    continue;
1861
1862
1863
1864
1865
	    /* 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. */
1866
	    } else if (!ISSET(RESTRICTED) && filename[0] != '\0'
1867
#ifndef NANO_SMALL
1868
		&& (exiting || !ISSET(MARK_ISSET))
1869
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1870
		) {
1871
		i = do_yesno(FALSE, _("Save file under DIFFERENT NAME ? "));
1872
1873
1874
		if (i == 0 || i == -1)
		    continue;
	    }
1875
	}
Chris Allegretta's avatar
Chris Allegretta committed
1876

1877
#ifndef NANO_SMALL
1878
1879
1880
1881
	/* 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. */
1882
	if (!ISSET(RESTRICTED) && !exiting && ISSET(MARK_ISSET))
1883
	    i = write_marked(answer, FALSE, append);
1884
	else
1885
#endif /* !NANO_SMALL */
1886
	    i = write_file(answer, FALSE, append, FALSE);
1887

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

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

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

1910
    if (buf[0] == '~') {
1911
1912
1913
1914
1915
1916
1917
	size_t i;
	const struct passwd *userdata;

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

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

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

1936
1937
1938
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1939
    return dirtmp;
1940
1941
}

Chris Allegretta's avatar
Chris Allegretta committed
1942
#ifndef DISABLE_TABCOMP
1943
1944
1945
/* Tack a slash onto the string we're completing if it's a directory.  We
 * assume there is room for one more character on the end of buf.  The
 * return value says whether buf is a directory. */
1946
int append_slash_if_dir(char *buf, bool *lastwastab, int *place)
1947
{
1948
    char *dirptr = real_dir_from_tilde(buf);
1949
    struct stat fileinfo;
1950
    int ret = 0;
1951

1952
    assert(dirptr != buf);
1953

1954
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
1955
	strncat(buf, "/", 1);
1956
	(*place)++;
1957
	/* now we start over again with # of tabs so far */
1958
	*lastwastab = FALSE;
1959
	ret = 1;
1960
1961
    }

1962
    free(dirptr);
1963
    return ret;
1964
}
Chris Allegretta's avatar
Chris Allegretta committed
1965
1966

/*
Chris Allegretta's avatar
Chris Allegretta committed
1967
1968
 * These functions (username_tab_completion(), cwd_tab_completion(), and
 * input_tab()) were taken from busybox 0.46 (cmdedit.c).  Here is the
1969
 * notice from that file:
Chris Allegretta's avatar
Chris Allegretta committed
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
 *
 * Termios command line History and Editting, originally
 * intended for NetBSD sh (ash)
 * Copyright (c) 1999
 *      Main code:            Adam Rogoyski <rogoyski@cs.utexas.edu>
 *      Etc:                  Dave Cinege <dcinege@psychosis.com>
 *  Majorly adjusted/re-written for busybox:
 *                            Erik Andersen <andersee@debian.org>
 *
 * You may use this code as you wish, so long as the original author(s)
 * are attributed in any redistributions of the source code.
 * This code is 'as is' with no warranty.
 * This code may safely be consumed by a BSD or GPL license.
 */

char **username_tab_completion(char *buf, int *num_matches)
{
1987
    char **matches = (char **)NULL;
1988
    char *matchline = NULL;
1989
    struct passwd *userdata;
1990

1991
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1992
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
1993

1994
    strcat(buf, "*");
1995

1996
    while ((userdata = getpwent()) != NULL) {
1997

1998
	if (check_wildcard_match(userdata->pw_name, &buf[1])) {
1999
2000
2001
2002

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

2004
2005
2006
2007
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2008
	    if (operating_dir != NULL) {
2009
		if (check_operating_dir(userdata->pw_dir, TRUE) != 0)
2010
2011
2012
2013
		    continue;
	    }
#endif

2014
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2015
2016
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
2017
	    ++(*num_matches);
2018

2019
2020
2021
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2022
	}
2023
2024
    }
    endpwent();
2025

2026
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2027
2028
2029
2030
2031
2032
2033
}

/* This was originally called exe_n_cwd_tab_completion, but we're not
   worried about executables, only filenames :> */

char **cwd_tab_completion(char *buf, int *num_matches)
{
Chris Allegretta's avatar
Chris Allegretta committed
2034
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2035
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2036
2037
2038
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2039
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2040
2041
2042
2043

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

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

Chris Allegretta's avatar
Chris Allegretta committed
2051
2052
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2053
2054
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2055

Chris Allegretta's avatar
Chris Allegretta committed
2056
    } else {
2057

Chris Allegretta's avatar
Chris Allegretta committed
2058
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2059
2060
2061
2062
2063
2064
	    return matches;
	else
	    tmp = buf;
    }

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

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

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


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

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

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

#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match; to
	       properly do operating directory checking, we have to add the
	       directory name to the beginning of the proposed match
	       before we check it */

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

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
2137
2138
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
2139
2140
char *input_tab(char *buf, int place, bool *lastwastab, int *newplace,
	bool *list)
Chris Allegretta's avatar
Chris Allegretta committed
2141
2142
{
    /* Do TAB completion */
2143
    static int num_matches = 0, match_matches = 0;
2144
    static char **matches = (char **)NULL;
2145
    int pos = place, i = 0, col = 0, editline = 0;
2146
    int longestname = 0, is_dir = 0;
2147
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2148

2149
    *list = FALSE;
2150

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

2154
	*lastwastab = TRUE;
2155

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

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

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

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

	/* If the word starts with `~' and there is no slash in the word, 
	 * then try completing this word as a username. */

Chris Allegretta's avatar
Chris Allegretta committed
2180
2181
2182
2183
2184
	/* If the original string begins with a tilde, and the part
	   we're trying to tab-complete doesn't contain a slash, copy
	   the part we're tab-completing into buf, so tab completion
	   will result in buf's containing only the tab-completed
	   username. */
2185
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2186
	    buf = mallocstrcpy(buf, tmp);
2187
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2188
	}
2189
2190
2191
2192
2193
2194
	/* If we're in the middle of the original line, copy the string
	   only up to the cursor position into buf, so tab completion
	   will result in buf's containing only the tab-completed
	   path/filename. */
	else if (strlen(buf) > strlen(tmp))
	    buf = mallocstrcpy(buf, tmp);
Chris Allegretta's avatar
Chris Allegretta committed
2195
2196
2197

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

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

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

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

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

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

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

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

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

2239
2240
2241
2242
2243
2244
2245
	    /* if an exact match is typed in and Tab is pressed,
	       *newplace will now be negative; in that case, make it
	       zero, so that the cursor will stay where it is instead of
	       moving backward */
	    if (*newplace < 0)
		*newplace = 0;

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

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

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

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

2265
	    while (TRUE) {
2266
2267
2268
2269
2270
2271
2272
2273
		match_matches = 0;

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

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

2298
	    editline = 0;
2299

2300
2301
2302
2303
2304
2305
2306
2307
	    /* Figure out the length of the longest filename */
	    for (i = 0; i < num_matches; i++)
		if (strlen(matches[i]) > longestname)
		    longestname = strlen(matches[i]);

	    if (longestname > COLS - 1)
		longestname = COLS - 1;

2308
	    foo = charalloc(longestname + 5);
2309

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

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

		strcat(foo, "  ");

2321
2322
		/* Disable el cursor */
		curs_set(0);
2323
2324
2325
2326
2327
2328
		/* now, put the match on the screen */
		waddnstr(edit, foo, strlen(foo));
		col += strlen(foo);

		/* And if the next match isn't going to fit on the
		   line, move to the next one */
2329
		if (col > COLS - longestname && i + 1 < num_matches) {
2330
2331
		    editline++;
		    wmove(edit, editline, 0);
2332
2333
2334
2335
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2336
2337
2338
		    col = 0;
		}
	    }
2339
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2340
	    wrefresh(edit);
2341
	    *list = TRUE;
2342
2343
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2344
2345
    }

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

2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
/* Only print the last part of a path; isn't there a shell
 * command for this? */
const char *tail(const char *foo)
{
    const char *tmp = foo + strlen(foo);

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

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

    return tmp;
}

2370
#ifndef DISABLE_BROWSER
2371
/* Our sort routine for file listings -- sort directories before
2372
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2373
2374
int diralphasort(const void *va, const void *vb)
{
2375
2376
2377
2378
    struct stat fileinfo;
    const char *a = *(char *const *)va, *b = *(char *const *)vb;
    int aisdir = stat(a, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
    int bisdir = stat(b, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode);
Chris Allegretta's avatar
Chris Allegretta committed
2379

2380
    if (aisdir != 0 && bisdir == 0)
2381
	return -1;
2382
    if (aisdir == 0 && bisdir != 0)
2383
	return 1;
2384

2385
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2386
2387
}

2388
/* Free our malloc()ed memory */
2389
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2390
{
2391
2392
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2393
2394
2395
    free(array);
}

2396
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2397
2398
void striponedir(char *foo)
{
2399
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2400

2401
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2402
    /* Don't strip the root dir */
2403
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2404
2405
	return;

2406
2407
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2408
    if (*tmp == '/')
2409
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2410
2411
2412
2413
2414

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

    if (tmp != foo)
2415
2416
2417
2418
2419
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2420
    }
Chris Allegretta's avatar
Chris Allegretta committed
2421
2422
}

2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
int readable_dir(const char *path)
{
    DIR *dir = opendir(path);

    /* If dir is NULL, don't do closedir(), since that changes errno. */
    if (dir != NULL)
	closedir(dir);
    return dir != NULL;
}

2433
/* Initialize the browser code, including the list of files in *path */
2434
char **browser_init(const char *path, int *longest, int *numents)
2435
2436
2437
{
    DIR *dir;
    struct dirent *next;
2438
    char **filelist;
2439
    int i = 0;
2440
    size_t path_len;
2441
2442

    dir = opendir(path);
2443
    if (dir == NULL)
2444
2445
2446
2447
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
2448
	if (strcmp(next->d_name, ".") == 0)
2449
2450
2451
2452
2453
2454
2455
2456
	   continue;
	(*numents)++;
	if (strlen(next->d_name) > *longest)
	    *longest = strlen(next->d_name);
    }
    rewinddir(dir);
    *longest += 10;

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

2459
    if (strcmp(path, "/") == 0)
2460
2461
2462
	path = "";
    path_len = strlen(path);

2463
    while ((next = readdir(dir)) != NULL) {
2464
	if (strcmp(next->d_name, ".") == 0)
2465
2466
	   continue;

2467
2468
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2469
2470
	i++;
    }
2471
    closedir(dir);
2472
2473
2474
2475
2476
2477
2478

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2479
/* Our browser function.  inpath is the path to start browsing from */
2480
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2481
2482
2483
2484
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2485
2486
2487
    int numents = 0, i = 0, j = 0, longest = 0, abort = 0, col = 0;
    int selected = 0, editline = 0, width = 0, filecols = 0, lineno = 0;
    int kbinput = ERR;
2488
    bool meta_key, func_key;
2489
    char **filelist = (char **)NULL;
2490
#ifndef DISABLE_MOUSE
2491
2492
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2493

2494
2495
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2496
2497
2498
    /* If path isn't the same as inpath, we are being passed a new
	dir as an arg.  We free it here so it will be copied from 
	inpath below */
2499
    if (path != NULL && strcmp(path, inpath) != 0) {
Chris Allegretta's avatar
Chris Allegretta committed
2500
2501
2502
2503
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2504
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2505
    if (path == NULL)
2506
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2507
2508

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

2511
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2512
2513
2514
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2515
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2516
2517
2518
2519
2520
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2521
2522

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2523
    do {
2524
2525
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2526

2527
	check_statblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2528

2529
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2530
	currshortcut = browser_list;
2531
2532
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2533
2534
 	editline = 0;
	col = 0;
2535
	    
2536
	/* Compute line number we're on now, so we don't divide by zero later */
2537
2538
2539
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2540
2541

	switch (kbinput) {
2542

2543
#ifndef DISABLE_MOUSE
2544
	case KEY_MOUSE:
2545
	    if (getmouse(&mevent) == ERR)
2546
		return retval;
2547
2548
2549
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2550
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2551
2552
2553
2554
		int selectedbackup = selected;

		mevent.y -= 2;

2555
2556
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2557
		selected = (lineno / editwinrows) * editwinrows * width
2558
2559
2560
2561
2562
2563
			+ 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--;
2564
2565
2566

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2567
		if (selected > numents - 1)
2568
		    selected = numents - 1;
2569
		else if (selectedbackup == selected)
2570
		    unget_kbinput('s', FALSE);	/* Unget the 'select' key */
2571
2572
2573
2574
	    } else {	/* Must be clicking a shortcut */
		int mouse_x, mouse_y;
		get_mouseinput(&mouse_x, &mouse_y, TRUE);
	    }
2575

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

2630
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2631
2632
2633
	    /* Note: the selected file can be outside the operating
	     * directory if it is .. or if it is a symlink to 
	     * directory outside the operating directory. */
2634
	    if (check_operating_dir(filelist[selected], FALSE) != 0) {
2635
2636
		statusbar(_("Can't go outside of %s in restricted mode"),
			operating_dir);
2637
2638
		beep();
		break;
2639
2640
2641
	    }
#endif

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

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

2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
	    new_path = mallocstrcpy(NULL, filelist[selected]);

	    if (strcmp("..", tail(new_path)) == 0) {
		/* They want to go up a level, so strip off .. and the
		   current dir */
		striponedir(new_path);
		/* SPK for '.' path, get the current path via getcwd */
		if (strcmp(new_path, ".") == 0) {
		    free(new_path);
		    new_path = getcwd(NULL, PATH_MAX + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2665
		}
2666
2667
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2668

2669
2670
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
2671
2672
		statusbar(_("Can't open \"%s\": %s"), new_path,
			strerror(errno));
2673
2674
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2675
	    }
2676
2677
2678
2679
2680
2681
2682

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

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

	    if (j < 0) {
2698
		statusbar(_("Cancelled"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2699
2700
2701
		break;
	    }

2702
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2703

2704
2705
2706
	    if (new_path[0] != '/') {
		new_path = charealloc(new_path, strlen(path) + strlen(answer) + 2);
		sprintf(new_path, "%s/%s", path, answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2707
2708
	    }

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

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

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

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

Rocco Corsi's avatar
   
Rocco Corsi committed
2743
2744
	blank_edit();

2745
	if (width != 0)
Chris Allegretta's avatar
Chris Allegretta committed
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
	    i = width * editwinrows * ((selected / width) / editwinrows);
	else
	    i = 0;

	wmove(edit, 0, 0);
	for (j = i; j < numents && editline <= editwinrows - 1; j++) {
	    filecols++;

	    strncpy(foo, tail(filelist[j]), strlen(tail(filelist[j])) + 1);
	    while (strlen(foo) < longest)
		strcat(foo, " ");
	    col += strlen(foo);

	    /* Put file info in the string also */
2760
2761
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2762
2763
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2764
2765
2766
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2767
2768
2769
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
2770
		    stat(filelist[j], &st);
2771
2772
2773
2774
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2775
2776
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2777
			(int) st.st_size);
2778
2779
2780
2781
2782
2783
		else if (st.st_size >= (1 << 30)) /* at least 1 gig */
		    sprintf(foo + longest - 7, "%4d GB", 
			(int) st.st_size >> 30);
		else if (st.st_size >= (1 << 20)) /* at least 1 meg */
		    sprintf(foo + longest - 7, "%4d MB", 
			(int) st.st_size >>     20);
2784
		else /* It's more than 1 k and less than a meg */
2785
2786
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2787
2788
	    }

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
2818
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2819
2820
2821
2822
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2823

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

2834
2835
2836
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2837

2838
2839
2840
2841
2842
2843
2844
2845
    /*
     * Perhaps path is a directory.  If so, we will pass that to
     * do_browser.  Otherwise, perhaps path is a directory / a file.  So
     * we try stripping off the last path element.  If it still isn't a
     * directory, just use the current directory. */

    if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	striponedir(path);
2846
2847
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2848
	    path = getcwd(NULL, PATH_MAX + 1);
2849
	}
2850
    }
2851

2852
2853
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2854
    if (check_operating_dir(path, FALSE) != 0)
2855
2856
2857
2858
2859
2860
2861
2862
2863
	path = mallocstrcpy(path, operating_dir);
#endif

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

2868
#if !defined(NANO_SMALL) && defined(ENABLE_NANORC)
2869
2870
2871
/* 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)
2872
{
2873
    char *nanohist = NULL;
2874

2875
2876
    if (homedir != NULL) {
	size_t homelen = strlen(homedir);
2877

2878
2879
2880
	nanohist = charalloc(homelen + 15);
	strcpy(nanohist, homedir);
	strcpy(nanohist + homelen, "/.nano_history");
Chris Allegretta's avatar
Chris Allegretta committed
2881
    }
2882
2883
2884
2885
2886
2887
    return nanohist;
}

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

2889
    /* assume do_rcfile() has reported missing home dir */
2890
2891
    if (nanohist != NULL) {
	FILE *hist = fopen(nanohist, "r");
Chris Allegretta's avatar
Chris Allegretta committed
2892

2893
	if (hist == NULL) {
2894
	    if (errno != ENOENT) {
2895
2896
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2897
		rcfile_error(N_("Error reading %s: %s"), nanohist, strerror(errno));
2898
2899
2900
		fprintf(stderr, _("\nPress Return to continue starting nano\n"));
		while (getchar() != '\n')
		    ;
2901
	    }
2902
	} else {
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
	    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
2917
2918
2919
		    history = &replace_history;
	    }
	    fclose(hist);
2920
	    free(line);
2921
2922
	    UNSET(HISTORY_CHANGED);
	}
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
	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;
2939
    }
2940
    return TRUE;
2941
2942
2943
2944
2945
}

/* save histories to ~/.nano_history */
void save_history(void)
{
2946
    char *nanohist;
2947
2948

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

2953
2954
2955
2956
    nanohist = histfilename();

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

2958
	if (hist == NULL)
2959
	    rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2960
	else {
2961
2962
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
2963
2964
2965
2966

	    if (!writehist(hist, &search_history) ||
		    putc('\n', hist) == EOF ||
		    !writehist(hist, &replace_history))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2967
		rcfile_error(N_("Error writing %s: %s"), nanohist, strerror(errno));
2968
2969
2970
2971
2972
	    fclose(hist);
	}
	free(nanohist);
    }
}
2973
#endif /* !NANO_SMALL && ENABLE_NANORC */