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

22
23
#include "config.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return fileptr;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return 1;
}

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

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

    return 1;
}

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

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

409
    while (TRUE) {
410
411

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

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

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

    return buf;
}

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

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

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

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

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

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

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

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

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

503
	if (i == NANO_EXTCMD_KEY) {
504
505
	    char *ans = mallocstrcpy(NULL, answer);
	    int ts = statusq(TRUE, extcmd_list, ans, NULL, 
506
		_("Command to execute"));
507
508
509

	    free(ans);

510
	    if (ts  == -1 || answer == NULL || answer[0] == '\0') {
511
		statusbar(_("Cancelled"));
512
513
514
515
		display_main_list();
		return 0;
	    }
	}
516
#endif /* !NANO_SMALL */
517
518
519
520
#ifndef DISABLE_BROWSER
	if (i == NANO_TOFILES_KEY) {
	    char *tmp = do_browse_from(answer);

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

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

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

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

565
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
566
567
568
569
570
571
572
573
574
575
576
577
578
579
	if (loading_file) {
	    /* if there was an error opening the file, free() realname,
	       free() fileage (which now points to the new buffer we
	       created to hold the file), reload the buffer we had open
	       before, and skip the insertion; otherwise, save realname
	       in filename and continue the insertion */
	    if (i == -1) {
		free(realname);
		free(fileage);
		load_open_file();
		goto skip_insert;
	    } else
		filename = mallocstrcpy(filename, realname);
	}
580
581
#endif

582
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
583

Chris Allegretta's avatar
Chris Allegretta committed
584
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
585
	dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
586
#endif
587

588
#ifdef ENABLE_MULTIBUFFER
589
	if (loading_file)
590
	    load_file(0);
591
592
593
	else
#endif
	    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
594

595
#ifdef ENABLE_MULTIBUFFER
596
597
598
599
600
601
602
603
604
605
	/* If we've loaded another file, update the titlebar's contents */
	if (loading_file) {
	    clearok(topwin, FALSE);
	    titlebar(NULL);

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

Chris Allegretta's avatar
Chris Allegretta committed
606
607
608
609
610
611
612
613
614
615
616
#ifdef ENABLE_MULTIBUFFER
	if (!loading_file) {
#endif

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

#ifdef ENABLE_MULTIBUFFER
	}
#endif

617
	/* If we've gone off the bottom, recenter; otherwise, just redraw */
618
	edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
619
620
621

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

Chris Allegretta's avatar
Chris Allegretta committed
625
626
627
628
#ifdef ENABLE_MULTIBUFFER
  skip_insert:
#endif

629
630
631
632
633
    free(inspath);
    inspath = NULL;

    display_main_list();
    return i;
Chris Allegretta's avatar
Chris Allegretta committed
634
635
}

636
637
638
int do_insertfile_void(void)
{
    int result = 0;
639
#ifdef ENABLE_MULTIBUFFER
640
641
642
643
644
645
646
647
    if (ISSET(VIEW_MODE)) {
	if (ISSET(MULTIBUFFER))
	    result = do_insertfile(1);
	else
	    statusbar(_("Key illegal in non-multibuffer mode"));
    }
    else
	result = do_insertfile(ISSET(MULTIBUFFER));
648
649
650
651
652
653
654
655
#else
    result = do_insertfile(0);
#endif

    display_main_list();
    return result;
}

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

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

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

    return newnode;
}

/* Splice a node into an existing openfilestruct. */
void splice_opennode(openfilestruct *begin, openfilestruct *newnode,
		     openfilestruct *end)
{
    newnode->next = end;
    newnode->prev = begin;
    begin->next = newnode;
    if (end != NULL)
	end->prev = newnode;
}

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

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

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

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

/* Deallocate all memory associated with this and later files,
 * including the lines of text. */
void free_openfilestruct(openfilestruct *src)
{
    if (src != NULL) {
	while (src->next != NULL) {
	    src = src->next;
	    delete_opennode(src->prev);
#ifdef DEBUG
716
	    fprintf(stderr, "%s: free'd a node, YAY!\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
717
718
719
720
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
721
	fprintf(stderr, "%s: free'd last node.\n", "delete_opennode()");
Chris Allegretta's avatar
Chris Allegretta committed
722
723
724
725
#endif
    }
}

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

735
    if (fileage == NULL || current == NULL || filename == NULL)
736
737
738
	return 1;

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

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

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

    /* save current filename */
757
    open_files->filename = mallocstrcpy(open_files->filename, filename);
758

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

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

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

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

800
801
802
    /* 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
803
804
805
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
806
    }
807
808

#ifdef DEBUG
809
    fprintf(stderr, "filename is %s\n", open_files->filename);
810
811
812
813
814
815
816
817
818
819
820
821
#endif

    return 0;
}

/*
 * Read the current entry in the open_files structure and set up the
 * currently open file using that entry's information.  Return 0 on
 * success or 1 on error.
 */
int load_open_file(void)
{
822
    if (open_files == NULL)
823
824
825
826
	return 1;

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

Chris Allegretta's avatar
Chris Allegretta committed
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
    /* 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
852

Chris Allegretta's avatar
Chris Allegretta committed
853
854
855
856
#ifdef ENABLE_COLOR
    update_color();
#endif

857
858
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
859
    do_gotopos(open_files->file_lineno, open_files->file_current_x, open_files->file_current_y, open_files->file_placewewant);
860

Chris Allegretta's avatar
Chris Allegretta committed
861
    /* update the titlebar */
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
    clearok(topwin, FALSE);
    titlebar(NULL);

    /* now we're done */
    return 0;
}

/*
 * Open the previous entry in the open_files structure.  If closing_file
 * is zero, update the current entry before switching from it.
 * Otherwise, we are about to close that entry, so don't bother doing so.
 * Return 0 on success and 1 on error.
 */
int open_prevfile(int closing_file)
{
877
    if (open_files == NULL)
878
879
880
	return 1;

    /* if we're not about to close the current entry, update it before
881
       doing anything */
882
    if (!closing_file)
883
	add_open_file(1);
884

885
    if (open_files->prev == NULL && open_files->next == NULL) {
886
887
888

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
889
	    statusbar(_("No more open file buffers"));
890
891
892
	return 1;
    }

893
    if (open_files->prev != NULL) {
894
895
896
	open_files = open_files->prev;

#ifdef DEBUG
897
	fprintf(stderr, "filename is %s\n", open_files->filename);
898
899
900
901
#endif

    }

902
    else if (open_files->next != NULL) {
903
904

	/* if we're at the beginning, wrap around to the end */
905
	while (open_files->next != NULL)
906
907
908
	    open_files = open_files->next;

#ifdef DEBUG
909
	    fprintf(stderr, "filename is %s\n", open_files->filename);
910
911
912
913
914
915
#endif

    }

    load_open_file();

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

919
920
921
922
923
924
925
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

926
927
928
/* This function is used by the shortcut list. */
int open_prevfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
929
    return open_prevfile(0);
930
931
}

932
933
934
935
936
937
938
939
/*
 * Open the next entry in the open_files structure.  If closing_file is
 * zero, update the current entry before switching from it.  Otherwise, we
 * are about to close that entry, so don't bother doing so.  Return 0 on
 * success and 1 on error.
 */
int open_nextfile(int closing_file)
{
940
    if (open_files == NULL)
941
942
943
	return 1;

    /* if we're not about to close the current entry, update it before
944
       doing anything */
945
    if (!closing_file)
946
	add_open_file(1);
947

948
    if (open_files->prev == NULL && open_files->next == NULL) {
949
950
951

	/* only one file open */
	if (!closing_file)
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
952
	    statusbar(_("No more open file buffers"));
953
954
955
	return 1;
    }

956
    if (open_files->next != NULL) {
957
958
959
	open_files = open_files->next;

#ifdef DEBUG
960
	fprintf(stderr, "filename is %s\n", open_files->filename);
961
962
963
#endif

    }
964
    else if (open_files->prev != NULL) {
965
966

	/* if we're at the end, wrap around to the beginning */
967
	while (open_files->prev != NULL) {
968
969
970
	    open_files = open_files->prev;

#ifdef DEBUG
971
	    fprintf(stderr, "filename is %s\n", open_files->filename);
972
973
974
975
976
977
978
#endif

	}
    }

    load_open_file();

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

982
983
984
985
986
987
988
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

989
990
991
/* This function is used by the shortcut list. */
int open_nextfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
992
    return open_nextfile(0);
993
994
}

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

1004
    if (open_files == NULL)
1005
1006
	return 1;

Chris Allegretta's avatar
Chris Allegretta committed
1007
1008
1009
    /* 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 */
1010
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
1011
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
1012

1013
    tmp = open_files;
1014
1015
    if (open_nextfile(1)) {
	if (open_prevfile(1))
1016
1017
1018
	    return 1;
    }

1019
1020
    unlink_opennode(tmp);
    delete_opennode(tmp);
1021
1022
1023
1024
1025

    shortcut_init(0);
    display_main_list();
    return 0;
}
1026
#endif /* MULTIBUFFER */
1027

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

1044
    /* first, get the current directory, and tack a slash onto the end of
1045
       it, unless it turns out to be "/", in which case leave it alone */
1046
1047
1048
1049
1050
1051
1052

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

1053
    if (d_here != NULL) {
1054
1055

	align(&d_here);
1056
	if (strcmp(d_here, "/")) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1057
	    d_here = charealloc(d_here, strlen(d_here) + 2);
1058
1059
	    strcat(d_here, "/");
	}
1060
1061
1062
1063
1064

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

1067
	expanded_origpath = real_dir_from_tilde(origpath);
1068
	/* save the value of origpath in both d_there and d_there_file */
1069
1070
1071
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1072

1073
1074
1075
1076
1077
1078
	/* 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
1079
		d_there = charealloc(d_there, strlen(d_there) + 2);
1080
		strcat(d_there, "/");
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1081
		d_there_file = charealloc(d_there_file, strlen(d_there_file) + 2);
1082
1083
1084
1085
		strcat(d_there_file, "/");
	    }
	}

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

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

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1111
1112
1113
		/* 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 */
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123

		free(d_there);

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

		align(&d_there);
1124
		if (d_there != NULL) {
1125
1126
1127
1128

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
		    if (strcmp(d_there, "/")) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1129
			d_there = charealloc(d_there, strlen(d_there) + 2);
1130
1131
			strcat(d_there, "/");
		    }
1132
1133
1134
		}
		else
		    return NULL;
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
	    }

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

1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
	/* 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);
	}
1156
1157
1158
1159
1160
1161
1162
1163
1164

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

    return newpath;
}
1165
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1166
1167
1168

#ifndef DISABLE_SPELLER
/*
1169
1170
1171
 * 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.
1172
 */
1173
char *check_writable_directory(const char *path)
1174
{
1175
    char *full_path = get_full_path(path);
1176
    int writable;
1177
1178
    struct stat fileinfo;

1179
    /* if get_full_path() failed, return NULL */
1180
    if (full_path == NULL)
1181
	return NULL;
1182
1183
1184

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

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

    /* 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
1204
1205
1206
1207
1208
1209
1210
 * 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.
1211
 */
1212
1213
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1214
1215
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1216
    int filedesc;
1217

1218
1219
      /* 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,
1220
         leave full_tempdir set to NULL */
1221
    TMPDIR_env = getenv("TMPDIR");
1222
    if (TMPDIR_env != NULL && TMPDIR_env[0] != '\0')
1223
	full_tempdir = check_writable_directory(TMPDIR_env);
1224

1225
1226
1227
    /* 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 */
1228
    if (full_tempdir == NULL && dirname != NULL)
1229
	full_tempdir = check_writable_directory(dirname);
1230
1231
1232

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1233
    if (full_tempdir == NULL)
1234
	full_tempdir = check_writable_directory(P_tmpdir);
1235
1236

    /* if P_tmpdir didn't work, use /tmp instead */
1237
    if (full_tempdir == NULL) {
1238
1239
1240
1241
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
1242
    full_tempdir = charealloc(full_tempdir, strlen(full_tempdir) + 12);
1243
1244

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

    free(full_tempdir);
    return NULL;
1260
1261
}
#endif /* !DISABLE_SPELLER */
1262
1263

#ifndef DISABLE_OPERATINGDIR
1264
1265
1266
1267
1268
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

1269
    if (operating_dir == NULL)
1270
	return;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1271

1272
1273
1274
    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
1275
     * inaccessible, unset operating_dir. */
1276
    if (full_operating_dir == NULL || chdir(full_operating_dir) == -1) {
1277
1278
1279
1280
1281
1282
1283
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1284
/* Check to see if we're inside the operating directory.  Return 0 if we
1285
1286
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
1287
 * completion will work. */
1288
int check_operating_dir(const char *currpath, int allow_tabcomp)
1289
{
1290
1291
1292
1293
    /* 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. */
1294

1295
1296
1297
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1298

1299
    /* If no operating directory is set, don't bother doing anything. */
1300
    if (operating_dir == NULL)
1301
	return 0;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1302

1303
    assert(full_operating_dir != NULL);
1304
1305

    fullpath = get_full_path(currpath);
1306
1307
1308
1309
1310
1311
1312
1313

    /* 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. */
1314
    if (fullpath == NULL)
1315
	return allow_tabcomp;
1316
1317
1318
1319
1320

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

1321
1322
1323
1324
1325
    /* 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. */
1326
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1327
1328
	retval = 1;
    free(fullpath);	
1329
1330

    /* Otherwise, we're still inside it. */
1331
    return retval;
1332
}
1333
1334
#endif

1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
#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

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

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

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

1430
    assert(name != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1431
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1432
1433
1434
	statusbar(_("Cancelled"));
	return -1;
    }
1435
1436
    if (!tmp)
	titlebar(NULL);
1437

1438
    realname = real_dir_from_tilde(name);
Chris Allegretta's avatar
Chris Allegretta committed
1439

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

1449
1450
1451
1452
1453
1454
1455
    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)) {
1456
	statusbar(_("Cannot prepend or append to a symlink with --nofollow set"));
1457
1458
1459
	goto cleanup_and_exit;
    }

1460
    /* Save the state of file at the end of the symlink (if there is
1461
1462
     * one). */
    realexists = stat(realname, &st) != -1;
1463

1464
1465
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
1466
1467
1468
1469
1470
1471
1472
1473
     * 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)) {

1474
	FILE *backup_file;
1475
	char *backupname;
1476
	struct utimbuf filetime;
1477
	int copy_status;
1478

1479
	/* Save the original file's access and modification times. */
1480
1481
1482
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

1483
	/* Open the original file to copy to the backup. */
1484
	f = fopen(realname, "rb");
1485
	if (f == NULL) {
1486
	    statusbar(_("Error reading %s: %s"), realname,
1487
		strerror(errno));
1488
	    goto cleanup_and_exit;
1489
1490
	}

1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
	/* 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);
	}
1523

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

#ifdef DEBUG
1539
	fprintf(stderr, "Backing up %s to %s\n", realname, backupname);
1540
1541
#endif

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

1561
1562
1563
1564
1565
1566
    /* 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));
1567
	goto cleanup_and_exit;
1568
    }
1569

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

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

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

1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
	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);
1614
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1615
1616
1617
	}
    }

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

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

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1634
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1635
    if (f == NULL) {
1636
1637
	statusbar(_("Error writing %s: %s"), realname, strerror(errno));
	close(fd);
1638
	goto cleanup_and_exit;
1639
1640
    }

1641
    /* There might not be a magicline.  There won't be when writing out
1642
1643
1644
1645
1646
     * a selection. */
    assert(fileage != NULL && filebot != NULL);
    while (fileptr != filebot) {
	size_t data_len = strlen(fileptr->data);
	size_t size;
1647

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

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

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

1656
	if (size < data_len) {
1657
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1658
	    fclose(f);
1659
	    goto cleanup_and_exit;
1660
	}
1661
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1662
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1663
1664
1665
1666
1667
	    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
1668
1669

	if (!ISSET(MAC_FILE))
1670
#endif
1671
1672
1673
1674
1675
	    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
1676
1677
1678
1679
1680

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

1681
    /* If we're prepending, open the temp file, and append it to f. */
1682
    if (append == 2) {
1683
1684
1685
1686
1687
1688
1689
1690
	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);
1691
	}
1692
	if (f_source == NULL) {
1693
1694
	    statusbar(_("Error reading %s: %s"), tempname, strerror(errno));
	    fclose(f);
1695
	    goto cleanup_and_exit;
1696
1697
	}

1698
1699
1700
	if (copy_file(f_source, f) == -1
		|| unlink(tempname) == -1) {
	    statusbar(_("Error writing %s: %s"), realname, strerror(errno));
1701
	    goto cleanup_and_exit;
1702
	}
1703
1704
1705
1706
    } 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
1707
    }
1708

1709
    if (!tmp && append == 0) {
Chris Allegretta's avatar
Chris Allegretta committed
1710
	if (!nonamechange) {
1711
	    filename = mallocstrcpy(filename, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1712
1713
#ifdef ENABLE_COLOR
	    update_color();
1714
1715
	    if (ISSET(COLOR_SYNTAX))
		edit_refresh();
Chris Allegretta's avatar
Chris Allegretta committed
1716
1717
#endif
	}
1718

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

    retval = 1;

  cleanup_and_exit:
    free(realname);
1733
    free(tempname);
1734
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1735
1736
}

1737
1738
1739
#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
1740
1741
1742
1743
1744
 * 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)
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
{
    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;

1786
    retval = write_file(name, tmp, append, TRUE);
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799

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

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

1800
int do_writeout(int exiting)
Chris Allegretta's avatar
Chris Allegretta committed
1801
{
1802
1803
    int i;
    int append = 0;
1804
#ifdef NANO_EXTRA
1805
    static int did_cred = FALSE;
1806
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1807

1808
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1809
    currshortcut = writefile_list;
1810
1811
#endif

1812
1813
1814
1815
1816
1817
    if (exiting && filename[0] != '\0' && ISSET(TEMP_OPT)) {
	i = write_file(filename, FALSE, 0, FALSE);
	if (i == 1) {
	    /* Write succeeded. */
	    display_main_list();
	    return 1;
1818
	}
Chris Allegretta's avatar
Chris Allegretta committed
1819
1820
    }

1821
#ifndef NANO_SMALL
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
    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);
1832
1833
	const char *formatstr, *backupstr;

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

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

1846
	/* Be nice to the translation folks. */
1847
	if (ISSET(MARK_ISSET) && !exiting) {
1848
	    if (append == 2)
1849
		msg = _("Prepend Selection to File");
Chris Allegretta's avatar
Chris Allegretta committed
1850
	    else if (append == 1)
1851
		msg = _("Append Selection to File");
1852
	    else
1853
1854
1855
		msg = _("Write Selection to File");
	} else
#endif /* !NANO_SMALL */
1856
	if (append == 2)
1857
	    msg = _("File Name to Prepend to");
Chris Allegretta's avatar
Chris Allegretta committed
1858
	else if (append == 1)
1859
	    msg = _("File Name to Append to");
1860
	else
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
	    msg = _("File Name to Write");

	i = statusq(TRUE, writefile_list,
#ifndef NANO_SMALL
		ans, NULL, "%s%s%s", msg, formatstr, backupstr
#else
		filename, "%s", msg
#endif
		);

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

1875
1876
1877
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
1878
	    return -1;
1879
	}
Chris Allegretta's avatar
Chris Allegretta committed
1880

1881
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1882
	if (i == NANO_TOFILES_KEY) {
1883
	    char *tmp = do_browse_from(answer);
1884

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

Chris Allegretta's avatar
Chris Allegretta committed
1914
#ifdef DEBUG
1915
	fprintf(stderr, "filename is %s\n", answer);
Chris Allegretta's avatar
Chris Allegretta committed
1916
#endif
1917
1918

#ifdef NANO_EXTRA
1919
	if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy")
1920
		&& !did_cred) {
1921
	    do_credits();
1922
	    did_cred = TRUE;
1923
1924
	    return -1;
	}
1925
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1926
	if (append == 0 && strcmp(answer, filename)) {
1927
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1928

1929
	    if (!stat(answer, &st)) {
1930
		i = do_yesno(FALSE, _("File exists, OVERWRITE ?"));
1931
1932
		if (i == 0 || i == -1)
		    continue;
1933
	    } else if (!ISSET(RESTRICTED) && filename[0] != '\0'
1934
#ifndef NANO_SMALL
1935
		&& (exiting || !ISSET(MARK_ISSET))
1936
#endif
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1937
		) {
1938
		i = do_yesno(FALSE, _("Save file under DIFFERENT NAME ?"));
1939
1940
1941
		if (i == 0 || i == -1)
		    continue;
	    }
1942
	}
Chris Allegretta's avatar
Chris Allegretta committed
1943

1944
#ifndef NANO_SMALL
1945
1946
	/* Here's where we allow the selected text to be written to
	 * a separate file. */
1947
	if (!ISSET(RESTRICTED) && !exiting && ISSET(MARK_ISSET))
1948
	    i = write_marked(answer, FALSE, append);
1949
	else
1950
#endif /* !NANO_SMALL */
1951
	    i = write_file(answer, FALSE, append, FALSE);
1952

1953
#ifdef ENABLE_MULTIBUFFER
1954
	/* If we're not about to exit, update the current entry in
1955
	 * the open_files structure. */
1956
1957
	if (!exiting)
	    add_open_file(1);
1958
#endif
1959
1960
	display_main_list();
	return i;
1961
    } /* while (TRUE) */
Chris Allegretta's avatar
Chris Allegretta committed
1962
1963
1964
1965
}

int do_writeout_void(void)
{
1966
    return do_writeout(FALSE);
Chris Allegretta's avatar
Chris Allegretta committed
1967
}
Chris Allegretta's avatar
Chris Allegretta committed
1968

1969
/* Return a malloc()ed string containing the actual directory, used
1970
1971
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1972
{
Chris Allegretta's avatar
Chris Allegretta committed
1973
    char *dirtmp = NULL;
1974

1975
    if (buf[0] == '~') {
1976
1977
1978
1979
1980
1981
1982
	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++)
	    ;

1983
1984
	/* Determine home directory using getpwuid() or getpwent(),
	   don't rely on $HOME */
Chris Allegretta's avatar
Chris Allegretta committed
1985
1986
1987
	if (i == 1)
	    userdata = getpwuid(geteuid());
	else {
1988
1989
1990
1991
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
			strncmp(userdata->pw_name, buf + 1, i - 1));
Chris Allegretta's avatar
Chris Allegretta committed
1992
1993
	}
	endpwent();
1994

1995
	if (userdata != NULL) {	/* User found */
1996
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1997
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1998
	}
1999
    }
2000

2001
2002
2003
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

2004
    return dirtmp;
2005
2006
}

Chris Allegretta's avatar
Chris Allegretta committed
2007
#ifndef DISABLE_TABCOMP
2008
2009
2010
/* Tack a slash onto the string we're completing if it's a directory.  We
 * assume there is room for one more character on the end of buf.  The
 * return value says whether buf is a directory. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2011
int append_slash_if_dir(char *buf, int *lastwastab, int *place)
2012
{
2013
    char *dirptr = real_dir_from_tilde(buf);
2014
    struct stat fileinfo;
2015
    int ret = 0;
2016

2017
    assert(dirptr != buf);
2018

2019
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
2020
	strncat(buf, "/", 1);
2021
	(*place)++;
2022
	/* now we start over again with # of tabs so far */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2023
	*lastwastab = 0;
2024
	ret = 1;
2025
2026
    }

2027
    free(dirptr);
2028
    return ret;
2029
}
Chris Allegretta's avatar
Chris Allegretta committed
2030
2031

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

2056
    *num_matches = 0;
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2057
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
2058

2059
    strcat(buf, "*");
2060

2061
    while ((userdata = getpwent()) != NULL) {
2062

2063
	if (check_wildcard_match(userdata->pw_name, &buf[1]) == TRUE) {
2064
2065
2066
2067

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

2069
2070
2071
2072
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

2073
2074
	    if (operating_dir != NULL) {
		if (check_operating_dir(userdata->pw_dir, 1) != 0)
2075
2076
2077
2078
		    continue;
	    }
#endif

2079
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2080
2081
2082
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
	    ++*num_matches;
2083

2084
2085
2086
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2087
	}
2088
2089
    }
    endpwent();
2090

2091
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2092
2093
2094
2095
2096
2097
2098
}

/* 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
2099
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
2100
    char **matches = (char **)NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2101
2102
2103
    DIR *dir;
    struct dirent *next;

David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2104
    matches = (char **)nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2105
2106
2107
2108

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

2109
    /* Okie, if there's a / in the buffer, strip out the directory part */
2110
    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2111
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2112
2113
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2114
	    tmp--;
2115

Chris Allegretta's avatar
Chris Allegretta committed
2116
2117
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2118
2119
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2120

Chris Allegretta's avatar
Chris Allegretta committed
2121
    } else {
2122
2123

#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2124
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
2125
#else
2126
	/* The better, but apparently segfault-causing way */
Chris Allegretta's avatar
Chris Allegretta committed
2127
	if ((dirname = getcwd(NULL, 0)) == NULL)
2128
#endif /* PATH_MAX */
Chris Allegretta's avatar
Chris Allegretta committed
2129
2130
2131
2132
2133
2134
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2135
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2136
2137
2138
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2139

Chris Allegretta's avatar
Chris Allegretta committed
2140
2141
2142
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2143
2144

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


Chris Allegretta's avatar
Chris Allegretta committed
2151
    dir = opendir(dirname);
2152
    if (dir == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2153
2154
2155
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
2156
	return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2157
2158
2159
2160
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2161
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2162
2163
2164
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
2165
2166
2167
2168

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2169
2170
2171
2172
2173
2174
2175
2176

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

2177
	    if (operating_dir != NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2178
2179
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2180
2181
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
2182
		if (check_operating_dir(tmp2, 1) != 0) {
2183
2184
2185
2186
2187
2188
2189
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2190
	    tmp2 = NULL;
2191
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2192
2193
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2194
	    ++*num_matches;
2195
2196
2197
2198

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2199
2200
	}
    }
2201
2202
    closedir(dir);
    free(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2203

2204
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
2205
2206
}

Chris Allegretta's avatar
Chris Allegretta committed
2207
2208
/* This function now has an arg which refers to how much the statusbar
 * (place) should be advanced, i.e. the new cursor pos. */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2209
char *input_tab(char *buf, int place, int *lastwastab, int *newplace, int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2210
2211
{
    /* Do TAB completion */
2212
    static int num_matches = 0, match_matches = 0;
2213
    static char **matches = (char **)NULL;
2214
    int pos = place, i = 0, col = 0, editline = 0;
2215
    int longestname = 0, is_dir = 0;
2216
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2217

2218
2219
    *list = 0;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2220
    if (*lastwastab == FALSE) {
2221
	char *tmp, *copyto, *matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2222

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2223
	*lastwastab = 1;
2224

Chris Allegretta's avatar
Chris Allegretta committed
2225
2226
	/* Make a local copy of the string -- up to the position of the
	   cursor */
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2227
2228
	matchbuf = charalloc(strlen(buf) + 2);
	memset(matchbuf, '\0', strlen(buf) + 2);
2229

2230
2231
	strncpy(matchbuf, buf, place);
	tmp = matchbuf;
Chris Allegretta's avatar
Chris Allegretta committed
2232
2233

	/* skip any leading white space */
2234
	while (*tmp && isblank(*tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2235
2236
2237
2238
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2239
2240
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2241
	    free(matches);
2242
	    matches = (char **)NULL;
2243
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2244
2245
2246
2247
2248
	}

	/* 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
2249
2250
2251
2252
2253
	/* 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. */
2254
	if (buf[0] == '~' && strchr(tmp, '/') == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2255
	    buf = mallocstrcpy(buf, tmp);
2256
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2257
	}
2258
2259
2260
2261
2262
2263
	/* 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
2264
2265
2266

	/* Try to match everything in the current working directory that
	 * matches.  */
2267
	if (matches == NULL)
Chris Allegretta's avatar
Chris Allegretta committed
2268
2269
2270
	    matches = cwd_tab_completion(tmp, &num_matches);

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

2273
2274
2275
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2276
	/* Did we find exactly one match? */
2277
	switch (num_matches) {
2278
	case 0:
2279
	    blank_edit();
2280
	    wrefresh(edit);
2281
2282
	    break;
	case 1:
2283

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

2286
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2287
2288
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2289
		tmp++;
2290
	    } else
2291
2292
		tmp = buf;

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

2296
	    if (is_dir != 0)
2297
		break;
2298
2299

	    copyto = tmp;
2300
2301
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2302
2303
		tmp++;

2304
	    /* write out the matched name */
2305
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2306
2307
	    *newplace += strlen(matches[0]) - pos;

2308
2309
2310
2311
2312
2313
2314
	    /* 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;

2315
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2316
	    append_slash_if_dir(buf, lastwastab, newplace);
2317

2318
2319
	    break;
	default:
2320
	    /* Check to see if all matches share a beginning, and, if so,
2321
	       tack it onto buf and then beep */
2322

2323
	    if (buf[0] != '\0' && strstr(buf, "/") != NULL) {
2324
2325
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2326
		tmp++;
2327
	    } else
2328
2329
		tmp = buf;

2330
	    for (pos = 0; *tmp == matches[0][pos] && *tmp != '\0' &&
2331
		 pos <= strlen(matches[0]); pos++)
2332
2333
		tmp++;

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

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

2367
	    editline = 0;
2368

2369
2370
2371
2372
2373
2374
2375
2376
	    /* 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;

2377
	    foo = charalloc(longestname + 5);
2378

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

2382
		/* make each filename shown be the same length as the longest
2383
		   filename, with two spaces at the end */
2384
2385
2386
2387
2388
2389
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

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

2415
2416
2417
2418
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
    if (*list == 0)
	edit_refresh();
2419
2420
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2421
}
2422
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2423

2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
/* 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;
}

2439
#ifndef DISABLE_BROWSER
2440
/* Our sort routine for file listings -- sort directories before
2441
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2442
2443
int diralphasort(const void *va, const void *vb)
{
2444
2445
2446
2447
    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
2448

2449
    if (aisdir != 0 && bisdir == 0)
2450
	return -1;
2451
    if (aisdir == 0 && bisdir != 0)
2452
	return 1;
2453

2454
    return strcasecmp(a, b);
Chris Allegretta's avatar
Chris Allegretta committed
2455
2456
}

2457
/* Free our malloc()ed memory */
2458
void free_charptrarray(char **array, size_t len)
Chris Allegretta's avatar
Chris Allegretta committed
2459
{
2460
2461
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2462
2463
2464
    free(array);
}

2465
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2466
2467
void striponedir(char *foo)
{
2468
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2469

2470
    assert(foo != NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2471
    /* Don't strip the root dir */
2472
    if (*foo == '\0' || strcmp(foo, "/") == 0)
Chris Allegretta's avatar
Chris Allegretta committed
2473
2474
	return;

2475
2476
    tmp = foo + strlen(foo) - 1;
    assert(tmp >= foo);
Chris Allegretta's avatar
Chris Allegretta committed
2477
    if (*tmp == '/')
2478
	*tmp = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
2479
2480
2481
2482
2483

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

    if (tmp != foo)
2484
2485
2486
2487
2488
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2489
    }
Chris Allegretta's avatar
Chris Allegretta committed
2490
2491
}

2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
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;
}

2502
/* Initialize the browser code, including the list of files in *path */
2503
char **browser_init(const char *path, int *longest, int *numents)
2504
2505
2506
{
    DIR *dir;
    struct dirent *next;
2507
    char **filelist;
2508
    int i = 0;
2509
    size_t path_len;
2510
2511

    dir = opendir(path);
2512
    if (dir == NULL)
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
	return NULL;

    *numents = 0;
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;
	(*numents)++;
	if (strlen(next->d_name) > *longest)
	    *longest = strlen(next->d_name);
    }
    rewinddir(dir);
    *longest += 10;

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

2528
2529
2530
2531
    if (!strcmp(path, "/"))
	path = "";
    path_len = strlen(path);

2532
2533
2534
2535
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;

2536
2537
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2538
2539
	i++;
    }
2540
    closedir(dir);
2541
2542
2543
2544
2545
2546
2547

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2548
/* Our browser function.  inpath is the path to start browsing from */
2549
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2550
2551
2552
2553
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
2554
    int numents = 0, i = 0, j = 0, kbinput = ERR, meta_key, longest = 0;
2555
2556
    int abort = 0, col = 0, selected = 0, editline = 0, width = 0;
    int filecols = 0, lineno = 0;
2557
    char **filelist = (char **)NULL;
2558
#ifndef DISABLE_MOUSE
2559
2560
    MEVENT mevent;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2561

2562
2563
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2564
2565
2566
    /* If path isn't the same as inpath, we are being passed a new
	dir as an arg.  We free it here so it will be copied from 
	inpath below */
Chris Allegretta's avatar
Chris Allegretta committed
2567
2568
2569
2570
2571
    if (path != NULL && strcmp(path, inpath)) {
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2572
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2573
    if (path == NULL)
2574
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2575
2576

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

2579
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2580
2581
2582
    qsort(filelist, numents, sizeof(char *), diralphasort);

    titlebar(path);
2583
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2584
2585
2586
2587
2588
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2589
2590

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2591
    do {
2592
2593
	char *new_path;
	    /* Used by the Go To Directory prompt. */
Rocco Corsi's avatar
   
Rocco Corsi committed
2594

2595
	check_statblank();
Rocco Corsi's avatar
   
Rocco Corsi committed
2596

2597
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2598
	currshortcut = browser_list;
2599
2600
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2601
2602
 	editline = 0;
	col = 0;
2603
	    
2604
	/* Compute line number we're on now, so we don't divide by zero later */
2605
2606
2607
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2608
2609

	switch (kbinput) {
2610

2611
#ifndef DISABLE_MOUSE
2612
	case KEY_MOUSE:
2613
	    if (getmouse(&mevent) == ERR)
2614
		return retval;
2615
2616
2617
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2618
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2619
2620
2621
2622
		int selectedbackup = selected;

		mevent.y -= 2;

2623
2624
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
2625
		selected = (lineno / editwinrows) * editwinrows * width
2626
2627
2628
2629
2630
2631
			+ 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--;
2632
2633
2634

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2635
		if (selected > numents - 1)
2636
		    selected = numents - 1;
2637
		else if (selectedbackup == selected)
2638
2639
2640
2641
		    ungetch('s');	/* Unget the 'select' key */
	    } else	/* Must be clicking a shortcut */
		do_mouse();

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

2690
#ifndef DISABLE_OPERATINGDIR
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2691
2692
2693
	    /* 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. */
2694
	    if (check_operating_dir(filelist[selected], 0) != 0) {
2695
2696
2697
		statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		beep();
		break;
2698
2699
2700
	    }
#endif

2701
2702
2703
2704
2705
	    if (stat(filelist[selected], &st) == -1) {
		statusbar(_("Can't open \"%s\": %s"), filelist[selected], strerror(errno));
		beep();
		break;
	    }
2706

2707
2708
2709
2710
	    if (!S_ISDIR(st.st_mode)) {
		retval = mallocstrcpy(retval, filelist[selected]);
		abort = 1;
		break;
2711
2712
	    }

2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
	    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
2723
		}
2724
2725
		striponedir(new_path);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2726

2727
2728
2729
2730
2731
	    if (!readable_dir(new_path)) {
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), new_path, strerror(errno));
		free(new_path);
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2732
	    }
2733
2734
2735
2736
2737
2738
2739

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

Rocco Corsi's avatar
   
Rocco Corsi committed
2740
2741
	/* Goto a specific directory */
	case NANO_GOTO_KEY:
2742
	case NANO_GOTO_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2743
2744
	case 'G': /* Pico compatibility */
	case 'g':
Rocco Corsi's avatar
   
Rocco Corsi committed
2745
	    curs_set(1);
2746
	    j = statusq(FALSE, gotodir_list, "",
Chris Allegretta's avatar
Chris Allegretta committed
2747
#ifndef NANO_SMALL
2748
		NULL,
Chris Allegretta's avatar
Chris Allegretta committed
2749
2750
#endif
		_("Goto Directory"));
2751
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2752
2753
2754
2755
2756
2757
2758
	    curs_set(0);

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

2759
	    new_path = real_dir_from_tilde(answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2760

2761
2762
2763
	    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
2764
2765
	    }

2766
#ifndef DISABLE_OPERATINGDIR
2767
	    if (check_operating_dir(new_path, 0) != 0) {
2768
2769
2770
2771
2772
2773
2774
		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
2775
2776
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
2777
		free(new_path);
Rocco Corsi's avatar
   
Rocco Corsi committed
2778
		break;
2779
	    }
Rocco Corsi's avatar
   
Rocco Corsi committed
2780
2781

	    /* Start over again with the new path value */
2782
2783
	    free_charptrarray(filelist, numents);
	    free(foo);
2784
2785
	    free(path);
	    path = new_path;
Rocco Corsi's avatar
   
Rocco Corsi committed
2786
2787
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2788
	/* Stuff we want to abort the browser */
2789
	case NANO_CANCEL_KEY:
2790
	case NANO_EXIT_KEY:
2791
	case NANO_EXIT_FKEY:
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2792
2793
	case 'E': /* Pico compatibility */
	case 'e':
2794
2795
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2796
2797
2798
2799
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2800
2801
	blank_edit();

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

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2846
	    /* Highlight the currently selected file/dir */
2847
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2848
		wattron(edit, A_REVERSE);
2849
2850
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2851
2852
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2853
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2854
2855
2856
2857
2858
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2859
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2860
2861
2862
2863
2864
2865
2866
2867
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
 	wrefresh(edit);
2868
    } while ((kbinput = get_kbinput(edit, &meta_key)) != NANO_EXIT_KEY && kbinput != NANO_EXIT_FKEY);
Chris Allegretta's avatar
Chris Allegretta committed
2869
2870
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2871
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2872
2873
    edit_refresh();

Chris Allegretta's avatar
Chris Allegretta committed
2874
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2875
2876
2877
2878
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2879

2880
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2881
 starts do_browser from there, else from the current dir */
2882
char *do_browse_from(const char *inpath)
2883
2884
{
    struct stat st;
2885
    char *bob;
2886
2887
2888
	/* The result of do_browser; the selected file name. */
    char *path;
	/* inpath, tilde expanded. */
2889

2890
2891
2892
    assert(inpath != NULL);

    path = real_dir_from_tilde(inpath);
2893

2894
2895
2896
2897
2898
2899
2900
2901
    /*
     * 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);
2902
2903
	if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
	    free(path);
2904
	    path = getcwd(NULL, PATH_MAX + 1);
2905
	}
2906
    }
2907

2908
2909
#ifndef DISABLE_OPERATINGDIR
    /* If the resulting path isn't in the operating directory, use that. */
2910
    if (check_operating_dir(path, 0) != 0)
2911
2912
2913
2914
2915
2916
2917
2918
2919
	path = mallocstrcpy(path, operating_dir);
#endif

    if (!readable_dir(path)) {
	beep();
	bob = NULL;
    } else
	bob = do_browser(path);
    free(path);
2920
    return bob;
2921
}
Chris Allegretta's avatar
Chris Allegretta committed
2922
#endif /* !DISABLE_BROWSER */
2923
2924
2925
2926
2927
2928

#ifndef NANO_SMALL
#ifdef ENABLE_NANORC
void load_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2929
    const struct passwd *userage = NULL;
2930
2931
    static char *nanohist;
    char *buf, *ptr;
Chris Allegretta's avatar
Chris Allegretta committed
2932
    char *homenv = getenv("HOME");
2933
2934
2935
    historyheadtype *history = &search_history;


Chris Allegretta's avatar
Chris Allegretta committed
2936
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2937
        nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
2938
2939
2940
2941
        sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2942
        nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
2943
        sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2944
2945
2946
2947
2948
    }

    /* assume do_rcfile has reported missing home dir */

    if (homenv != NULL || userage != NULL) {
2949
	hist = fopen(nanohist, "r");
2950
	if (hist == NULL) {
2951
2952
2953
            if (errno != ENOENT) {
		/* Don't save history when we quit. */
		UNSET(HISTORYLOG);
2954
		rcfile_error(_("Unable to open ~/.nano_history file: %s"), strerror(errno));
2955
	    }
2956
2957
2958
2959
2960
	    free(nanohist);
	} else {
	    buf = charalloc(1024);
	    while (fgets(buf, 1023, hist) != 0) {
		ptr = buf;
2961
		while (*ptr != '\n' && *ptr != '\0' && ptr < buf + 1023)
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
		    ptr++;
		*ptr = '\0';
		if (strlen(buf))
		    update_history(history, buf);
		else
		    history = &replace_history;
	    }
	    fclose(hist);
	    free(buf);
	    free(nanohist);
	    UNSET(HISTORY_CHANGED);
	}
    }
}

/* save histories to ~/.nano_history */
void save_history(void)
{
    FILE *hist;
Chris Allegretta's avatar
Chris Allegretta committed
2981
    const struct passwd *userage = NULL;
2982
    char *nanohist = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2983
    char *homenv = getenv("HOME");
2984
2985
2986
2987
2988
2989
2990
    historytype *h;

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

Chris Allegretta's avatar
Chris Allegretta committed
2991
    if (homenv != NULL) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2992
	nanohist = charealloc(nanohist, strlen(homenv) + 15);
Chris Allegretta's avatar
Chris Allegretta committed
2993
2994
2995
2996
	sprintf(nanohist, "%s/.nano_history", homenv);
    } else {
	userage = getpwuid(geteuid());
	endpwent();
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
2997
	nanohist = charealloc(nanohist, strlen(userage->pw_dir) + 15);
2998
	sprintf(nanohist, "%s/.nano_history", userage->pw_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2999
3000
3001
    }

    if (homenv != NULL || userage != NULL) {
3002
	hist = fopen(nanohist, "wb");
3003
	if (hist == NULL) {
3004
	    rcfile_msg(_("Unable to write ~/.nano_history file: %s"), strerror(errno));
3005
3006
3007
3008
	} else {
	    /* set rw only by owner for security ?? */
	    chmod(nanohist, S_IRUSR | S_IWUSR);
	    /* write oldest first */
3009
	    for (h = search_history.tail; h->prev; h = h->prev) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3010
		h->data = charealloc(h->data, strlen(h->data) + 2);
3011
3012
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
3013
		    rcfile_msg(_("Unable to write ~/.nano_history file: %s"), strerror(errno));
3014
3015
3016
3017
		    goto come_from;
		}
	    }
	    if (fputs("\n", hist) == EOF) {
3018
		    rcfile_msg(_("Unable to write ~/.nano_history file: %s"), strerror(errno));
3019
3020
		    goto come_from;
	    }
3021
	    for (h = replace_history.tail; h->prev; h = h->prev) {
David Lawrence Ramsey's avatar
   
David Lawrence Ramsey committed
3022
		h->data = charealloc(h->data, strlen(h->data) + 2);
3023
3024
		strcat(h->data, "\n");
		if (fputs(h->data, hist) == EOF) {
3025
		    rcfile_msg(_("Unable to write ~/.nano_history file: %s"), strerror(errno));
3026
3027
3028
		    goto come_from;
		}
	    }
3029
  come_from:
3030
3031
3032
3033
3034
3035
	    fclose(hist);
	}
	free(nanohist);
    }
}
#endif /* ENABLE_NANORC */
Chris Allegretta's avatar
Chris Allegretta committed
3036
#endif /* !NANO_SMALL */