files.c 72 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-2002 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>
Chris Allegretta's avatar
Chris Allegretta committed
36
#include <assert.h>
37
#include <pwd.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
#ifndef NANO_SMALL
static int fileformat = 0;	/* 0 = *nix, 1 = DOS, 2 = Mac */
#endif

Chris Allegretta's avatar
Chris Allegretta committed
45
/* Load file into edit buffer - takes data from file struct */
Chris Allegretta's avatar
Chris Allegretta committed
46
void load_file(int update)
Chris Allegretta's avatar
Chris Allegretta committed
47
48
{
    current = fileage;
49

50
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
51
    /* if update is zero, add a new entry to the open_files structure;
52
53
       otherwise, update the current entry (the latter is needed in the
       case of the alternate spell checker) */
Chris Allegretta's avatar
Chris Allegretta committed
54
    add_open_file(update);
55
56
#endif

57
58
59
60
#ifdef ENABLE_COLOR
    update_color();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
61
62
63
64
65
66
    wmove(edit, current_y, current_x);
}

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

#ifdef ENABLE_MULTIBUFFER
    /* if there aren't any entries in open_files, create the entry for
80
81
       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
82
       given no open_files entry */
83
    if (!open_files) {
84
	add_open_file(0);
85
86
87
88
89
90
91
92
93
	/* 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);
94
95
#endif

96
97
98
#ifdef ENABLE_COLOR
    update_color();
#endif
Chris Allegretta's avatar
Chris Allegretta committed
99
100
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
101
filestruct *read_line(char *buf, filestruct *prev, int *line1ins, int len)
Chris Allegretta's avatar
Chris Allegretta committed
102
103
104
105
{
    filestruct *fileptr;

    fileptr = nmalloc(sizeof(filestruct));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
106
107
108
109

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

110
    fileptr->data = charalloc(strlen(buf) + 2);
Chris Allegretta's avatar
Chris Allegretta committed
111
112
    strcpy(fileptr->data, buf);

113
#ifndef NANO_SMALL
114
115
116
    /* If it's a DOS file (CRLF), and file conversion isn't disabled,
       strip out the CR part */
    if (!ISSET(NO_CONVERT) && buf[strlen(buf) - 1] == '\r') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
117
	fileptr->data[strlen(buf) - 1] = '\0';
118
	totsize--;
119
120
121

	if (!fileformat)
	    fileformat = 1;
122
123
124
    }
#endif

Chris Allegretta's avatar
Chris Allegretta committed
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
    if (*line1ins) {
	/* Special case, insert with cursor on 1st line. */
	fileptr->prev = NULL;
	fileptr->next = fileage;
	fileptr->lineno = 1;
	*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!) */
	fileage = fileptr;
	edittop = fileptr;
    } else if (fileage == NULL) {
	fileage = fileptr;
	fileage->lineno = 1;
	fileage->next = fileage->prev = NULL;
	fileptr = filebot = fileage;
    } else if (prev) {
	fileptr->prev = prev;
	fileptr->next = NULL;
	fileptr->lineno = prev->lineno + 1;
	prev->next = fileptr;
    } else {
	die(_("read_line: not on first line and prev is NULL"));
    }

    return fileptr;
}

Chris Allegretta's avatar
Chris Allegretta committed
153
int read_file(FILE *f, const char *filename, int quiet)
Chris Allegretta's avatar
Chris Allegretta committed
154
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
155
    int num_lines = 0, len = 0;
156
    char input = 0; 		/* current input character */
Chris Allegretta's avatar
Chris Allegretta committed
157
158
159
160
    char *buf;
    long i = 0, bufx = 128;
    filestruct *fileptr = current, *tmp = NULL;
    int line1ins = 0;
161
    int input_int;
Chris Allegretta's avatar
Chris Allegretta committed
162

163
    buf = charalloc(bufx);
164
    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
165
166
167
168
169
170
171
172
173
174

    if (fileptr != NULL && fileptr->prev != NULL) {
	fileptr = fileptr->prev;
	tmp = fileptr;
    } else if (fileptr != NULL && fileptr->prev == NULL) {
	tmp = fileage;
	current = fileage;
	line1ins = 1;
    }
    /* Read the entire file into file struct */
175
    while ((input_int = getc(f)) != EOF) {
Chris Allegretta's avatar
Chris Allegretta committed
176
        input = (char)input_int;
Chris Allegretta's avatar
Chris Allegretta committed
177
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
178
179
	if (!ISSET(NO_CONVERT) && is_cntrl_char((int)input)
		&& input != '\t' && input != '\r' && input != '\n') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
180
181
182
183
	    /* If the file has binary chars in it, don't stupidly
	       assume it's a DOS or Mac formatted file! */
	    SET(NO_CONVERT);
	}
Chris Allegretta's avatar
Chris Allegretta committed
184
185
#endif

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
186
187
188
189
	/* calculate the total length of the line; it might have nulls in
	   it, so we can't just use strlen() */
	len++;

190
	if (input == '\n') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
191
192
193
194
195
196
197
198
199
200

	    /* don't count the newline in the line length */
	    len--;

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

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

201
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
202
	    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
203
	    i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
204
#ifndef NANO_SMALL
205
206
	/* 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
207
	} else if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r') {
208
	    fileformat = 2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
209
210
211
212
213
214
215
216
217
218

	    /* don't count the newline in the line length */
	    len--;

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

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

Chris Allegretta's avatar
Chris Allegretta committed
219
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
220
	    totsize++;
221
	    buf[0] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
222
	    buf[1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
223
224
	    i = 1;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
225
226
227
228
	} else {
	    /* Now we allocate a bigger buffer 128 characters at a time.
	       If we allocate a lot of space for one line, we may indeed 
	       have to use a buffer this big later on, so we don't
229
	       decrease it at all.  We do free it at the end, though. */
Chris Allegretta's avatar
Chris Allegretta committed
230
231
232
233
234

	    if (i >= bufx - 1) {
		buf = nrealloc(buf, bufx + 128);
		bufx += 128;
	    }
235
	    buf[i] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
236
	    buf[i + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
237
238
	    i++;
	}
239
240
241
242
243
244
245
	totsize++;
    }

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

    /* Did we not get a newline but still have stuff to do? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
249
    if (len > 0) {
250
251
252
253
254
255
256
257
#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 */
	if (!ISSET(NO_CONVERT) && buf[len - 1] == '\r' && !fileformat)
	    fileformat = 2;
#endif

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

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

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

286
    /* Did we try to insert a file of 0 bytes? */
287
288
289
290
291
292
293
294
295
296
297
298
299
    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
300
    }
301
302
303

#ifndef NANO_SMALL
    if (fileformat == 2)
304
	statusbar(__("Read %d line (Converted from Mac format)",
305
306
			"Read %d lines (Converted from Mac format)",
			num_lines), num_lines);
307
    else if (fileformat == 1)
308
	statusbar(__("Read %d line (Converted from DOS format)",
309
310
			"Read %d lines (Converted from DOS format)",
			num_lines), num_lines);
311
312
    else
#endif
313
	statusbar(__("Read %d line", "Read %d lines", num_lines),
314
			num_lines);
315

316
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
317
318

    free(buf);
319
    fclose(f);
Chris Allegretta's avatar
Chris Allegretta committed
320
321
322
323
324

    return 1;
}

/* Open the file (and decide if it exists) */
Chris Allegretta's avatar
Chris Allegretta committed
325
int open_file(const char *filename, int insert, int quiet)
Chris Allegretta's avatar
Chris Allegretta committed
326
327
{
    int fd;
328
    FILE *f;
Chris Allegretta's avatar
Chris Allegretta committed
329
330
    struct stat fileinfo;

Chris Allegretta's avatar
Chris Allegretta committed
331
    if (filename[0] == '\0' || stat(filename, &fileinfo) == -1) {
332
333
	if (insert && !quiet) {
	    statusbar(_("\"%s\" not found"), filename);
Chris Allegretta's avatar
Chris Allegretta committed
334
335
336
337
338
339
340
341
342
	    return -1;
	} else {
	    /* We have a new file */
	    statusbar(_("New File"));
	    new_file();
	}
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
	if (!quiet)
	    statusbar("%s: %s", strerror(errno), filename);
343
344
	if (!insert)
	    new_file();
Chris Allegretta's avatar
Chris Allegretta committed
345
346
	return -1;
    } else {			/* File is A-OK */
347
348
349
	if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) || 
		S_ISBLK(fileinfo.st_mode)) {
	    if (S_ISDIR(fileinfo.st_mode))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
350
		statusbar(_("\"%s\" is a directory"), filename);
351
352
353
354
	    else
		/* Don't open character or block files.  Sorry, /dev/sndstat! */
		statusbar(_("File \"%s\" is a device file"), filename);

Chris Allegretta's avatar
Chris Allegretta committed
355
356
	    if (!insert)
		new_file();
Chris Allegretta's avatar
Chris Allegretta committed
357
358
359
360
	    return -1;
	}
	if (!quiet)
	    statusbar(_("Reading File"));
361
362
363
364
365
366
	f = fdopen(fd, "rb"); /* Binary for our own line-end munging */
	if (!f) {
	    nperror("fdopen");
	    return -1;
	}
	read_file(f, filename, quiet);
367
368
369
#ifndef NANO_SMALL
	stat(filename, &originalfilestat);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
370
371
372
373
374
    }

    return 1;
}

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

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

Chris Allegretta's avatar
Chris Allegretta committed
388
    while (1) {
389
390

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

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

Chris Allegretta's avatar
Chris Allegretta committed
400
401
    /* We get here only if there is no possible save file. */
    buf[0] = '\0';
402
403
404
405

    return buf;
}

406
int do_insertfile(int loading_file)
Chris Allegretta's avatar
Chris Allegretta committed
407
{
Chris Allegretta's avatar
Chris Allegretta committed
408
    int i, old_current_x = current_x;
409
    char *realname = NULL;
410
411
412
413
    static char *inspath = NULL;

    if (inspath == NULL)
	inspath = mallocstrcpy(inspath, "");
Chris Allegretta's avatar
Chris Allegretta committed
414
415

    wrap_reset();
416

417
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
418
    currshortcut = insertfile_list;
419
420
#endif

421
#ifndef DISABLE_OPERATINGDIR
422
    if (operating_dir && strcmp(operating_dir, "."))
423
424
425
426
427
428
429
#ifdef ENABLE_MULTIBUFFER 
	if (ISSET(MULTIBUFFER))
	    i = statusq(1, insertfile_list, inspath, _("File to insert into new buffer [from %s] "),
		operating_dir);
	else
#endif
	    i = statusq(1, insertfile_list, inspath, _("File to insert [from %s] "),
430
		operating_dir);
431

432
    else
433
#endif
434
435
436
437
438
439
#ifdef ENABLE_MULTIBUFFER 
	if (ISSET(MULTIBUFFER))
	    i = statusq(1, insertfile_list, inspath, _("File to insert into new buffer [from ./] "));
	else
#endif
	    i = statusq(1, insertfile_list, inspath, _("File to insert [from ./] "));
440

Chris Allegretta's avatar
Chris Allegretta committed
441
    if (i != -1) {
442
	inspath = mallocstrcpy(inspath, answer);
Chris Allegretta's avatar
Chris Allegretta committed
443
#ifdef DEBUG
444
	fprintf(stderr, _("filename is %s\n"), answer);
Chris Allegretta's avatar
Chris Allegretta committed
445
446
#endif

447
#ifndef DISABLE_TABCOMP
448
	realname = real_dir_from_tilde(answer);
449
#else
450
	realname = mallocstrcpy(realname, answer);
451
#endif
452

453
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
454
	if (i == NANO_TOFILES_KEY) {
455
	    char *tmp = do_browse_from(realname);
456

457
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
458
	    currshortcut = insertfile_list;
459
#endif
460
#ifdef DISABLE_TABCOMP
461
	    realname = NULL;
462
#endif
463
464
465
466
	    if (tmp != NULL) {
		free(realname);
		realname = tmp;
	    } else
467
468
469
470
		return do_insertfile(loading_file);
	}
#endif

471
#ifndef DISABLE_OPERATINGDIR
472
473
474
475
	if (operating_dir && check_operating_dir(realname, 0)) {
	    statusbar(_("Can't insert file from outside of %s"),
			operating_dir);
	    return 0;
476
477
478
	}
#endif

479
#ifndef NANO_SMALL
480
#ifdef ENABLE_MULTIBUFFER
481
	if (i == TOGGLE_LOAD_KEY) {
482
483
484
485
486
	    /* don't allow toggling if we're in both view mode and
	       multibuffer mode now */
	    if (!ISSET(VIEW_MODE) || !ISSET(MULTIBUFFER))
		TOGGLE(MULTIBUFFER);
	    return do_insertfile(ISSET(MULTIBUFFER));
487
	}
488
#endif /* ENABLE_MULTIBUFFER */
489
490
491
	if (i == NANO_EXTCMD_KEY) {
	    int ts;
	    ts = statusq(1, extcmd_list, "", _("Command to execute "));
Chris Allegretta's avatar
Chris Allegretta committed
492
	    if (ts == -1  || answer == NULL || answer[0] == '\0') {
493
494
495
496
497
498
		statusbar(_("Cancelled"));
		UNSET(KEEP_CUTBUFFER);
		display_main_list();
		return 0;
	    }
	}
499
#endif /* !NANO_SMALL */
500

501
#ifdef ENABLE_MULTIBUFFER
502
	if (loading_file) {
503
504
	    /* update the current entry in the open_files structure */
	    add_open_file(1);
505
506
	    new_file();
	    UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
507
508
509
#ifndef NANO_SMALL
	    UNSET(MARK_ISSET);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
510
511
512
	}
#endif

513
#ifndef NANO_SMALL
514
	if (i == NANO_EXTCMD_KEY)
515
	    i = open_pipe(answer);
516
517
518
	else 
#endif /* NANO_SMALL */
	    i = open_file(realname, 1, loading_file);
519

520
#ifdef ENABLE_MULTIBUFFER
521
522
523
524
	if (loading_file)
	    filename = mallocstrcpy(filename, realname);
#endif

525
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
526

Chris Allegretta's avatar
Chris Allegretta committed
527
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
528
	dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
529
#endif
530

531
#ifdef ENABLE_MULTIBUFFER
532
	if (loading_file)
533
	    load_file(0);
534
535
536
	else
#endif
	    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
537
538

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

541
#ifdef ENABLE_MULTIBUFFER
542
543
544
545
546
547
548
549
550
551
	/* 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
552
553
554
555
556
557
558
559
560
561
562
#ifdef ENABLE_MULTIBUFFER
	if (!loading_file) {
#endif

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

#ifdef ENABLE_MULTIBUFFER
	}
#endif

563
	/* If we've gone off the bottom, recenter; otherwise, just redraw */
Chris Allegretta's avatar
Chris Allegretta committed
564
	if (current->lineno > editbot->lineno)
565
	    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
566
567
568
	else
	    edit_refresh();

569
570
571
572
#ifdef ENABLE_COLOR
	update_color();
#endif    

Chris Allegretta's avatar
Chris Allegretta committed
573
574
    } else {
	statusbar(_("Cancelled"));
575
	i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
576
    }
577
578
579
580
581
582
583

    free(inspath);
    inspath = NULL;

    UNSET(KEEP_CUTBUFFER);
    display_main_list();
    return i;
Chris Allegretta's avatar
Chris Allegretta committed
584
585
}

586
587
588
int do_insertfile_void(void)
{
    int result = 0;
589
#ifdef ENABLE_MULTIBUFFER
590
591
592
593
594
595
596
597
    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));
598
599
600
601
602
603
604
605
#else
    result = do_insertfile(0);
#endif

    display_main_list();
    return result;
}

606
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
/* Create a new openfilestruct node. */
openfilestruct *make_new_opennode(openfilestruct *prevnode)
{
    openfilestruct *newnode = nmalloc(sizeof(openfilestruct));

    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
	    fprintf(stderr, _("delete_opennode(): free'd a node, YAY!\n"));
#endif
	}
	delete_opennode(src);
#ifdef DEBUG
	fprintf(stderr, _("delete_opennode(): free'd last node.\n"));
#endif
    }
}

676
/*
677
 * Add/update an entry to the open_files openfilestruct.  If update is
678
 * zero, a new entry is created; otherwise, the current entry is updated.
679
 * Return 0 on success or 1 on error.
680
 */
681
int add_open_file(int update)
682
{
683
    openfilestruct *tmp;
684

685
    if (!fileage || !current || !filename)
686
687
688
	return 1;

    /* if no entries, make the first one */
689
690
    if (!open_files)
	open_files = make_new_opennode(NULL);
691
692
693
694
695
696
697

    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
698
	fprintf(stderr, _("filename is %s\n"), open_files->filename);
699
700
#endif

701
702
	tmp = make_new_opennode(NULL);
	splice_opennode(open_files, tmp, open_files->next);
703
704
705
706
	open_files = open_files->next;
    }

    /* save current filename */
707
    open_files->filename = mallocstrcpy(open_files->filename, filename);
708

709
710
711
712
713
#ifndef NANO_SMALL
    /* save the file's stat */
    open_files->originalfilestat = originalfilestat;
#endif

714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
    /* 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 */
730
    open_files->file_lineno = current->lineno;
731

Chris Allegretta's avatar
Chris Allegretta committed
732
733
734
735
736
737
738
739
740
741
742
743
744
745
    /* if we're updating, save current modification status (and marking
       status, if available) */
    if (update) {
#ifndef NANO_SMALL
	open_files->file_flags = (MODIFIED & ISSET(MODIFIED)) | (MARK_ISSET & ISSET(MARK_ISSET));
	if (ISSET(MARK_ISSET)) {
	    open_files->file_mark_beginbuf = mark_beginbuf;
	    open_files->file_mark_beginx = mark_beginx;
	}
#else
	open_files->file_flags = (MODIFIED & ISSET(MODIFIED));
#endif
    }

746
747
748
749
    /* if we're in view mode and updating, the file contents won't
       have changed, so we won't bother resaving the filestruct
       then; otherwise, we will */
    if (!(ISSET(VIEW_MODE) && !update)) {
Chris Allegretta's avatar
Chris Allegretta committed
750
751
752
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
753
    }
754
755

#ifdef DEBUG
756
    fprintf(stderr, _("filename is %s\n"), open_files->filename);
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
#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)
{
    if (!open_files)
	return 1;

    /* set up the filename, the file buffer, the total number of lines in
       the file, and the total file size */
774
    filename = mallocstrcpy(filename, open_files->filename);
775
776
777
#ifndef NANO_SMALL
    originalfilestat = open_files->originalfilestat;
#endif
778
    fileage = open_files->fileage;
779
    current = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
780
    filebot = open_files->filebot;
781
782
783
    totlines = open_files->file_totlines;
    totsize = open_files->file_totsize;

Chris Allegretta's avatar
Chris Allegretta committed
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
    /* 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
799

800
801
    /* restore full file position: line number, x-coordinate, y-
       coordinate, place we want */
802
    do_gotopos(open_files->file_lineno, open_files->file_current_x, open_files->file_current_y, open_files->file_placewewant);
803

Chris Allegretta's avatar
Chris Allegretta committed
804
    /* update the titlebar */
805
806
807
    clearok(topwin, FALSE);
    titlebar(NULL);

808
809
810
    /* if we're constantly displaying the cursor position, update it (and do so
       unconditionally, in the rare case that the character count is the same
       but the line count isn't) */
811
    if (ISSET(CONSTUPDATE))
812
	do_cursorpos(0);
813

814
815
816
817
#ifdef ENABLE_COLOR
    update_color();
#endif    

818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
    /* 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)
{
    if (!open_files)
	return 1;

    /* if we're not about to close the current entry, update it before
834
       doing anything */
835
    if (!closing_file)
836
	add_open_file(1);
837
838
839
840
841
842
843
844
845
846
847
848
849

    if (!open_files->prev && !open_files->next) {

	/* only one file open */
	if (!closing_file)
	    statusbar(_("No more open files"));
	return 1;
    }

    if (open_files->prev) {
	open_files = open_files->prev;

#ifdef DEBUG
850
	fprintf(stderr, _("filename is %s\n"), open_files->filename);
851
852
853
854
855
856
857
858
859
860
861
#endif

    }

    else if (open_files->next) {

	/* if we're at the beginning, wrap around to the end */
	while (open_files->next)
	    open_files = open_files->next;

#ifdef DEBUG
862
	    fprintf(stderr, _("filename is %s\n"), open_files->filename);
863
864
865
866
867
868
#endif

    }

    load_open_file();

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

872
873
874
875
876
877
878
#ifdef DEBUG
    dump_buffer(current);
#endif

    return 0;
}

879
880
881
/* This function is used by the shortcut list. */
int open_prevfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
882
    return open_prevfile(0);
883
884
}

885
886
887
888
889
890
891
892
893
894
895
896
/*
 * 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)
{
    if (!open_files)
	return 1;

    /* if we're not about to close the current entry, update it before
897
       doing anything */
898
    if (!closing_file)
899
	add_open_file(1);
900
901
902
903
904
905
906
907
908
909
910
911
912

    if (!open_files->prev && !open_files->next) {

	/* only one file open */
	if (!closing_file)
	    statusbar(_("No more open files"));
	return 1;
    }

    if (open_files->next) {
	open_files = open_files->next;

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

    }
    else if (open_files->prev) {

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

#ifdef DEBUG
924
	    fprintf(stderr, _("filename is %s\n"), open_files->filename);
925
926
927
928
929
930
931
#endif

	}
    }

    load_open_file();

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

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

    return 0;
}

942
943
944
/* This function is used by the shortcut list. */
int open_nextfile_void(void)
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
945
    return open_nextfile(0);
946
947
}

948
949
/*
 * Delete an entry from the open_files filestruct.  After deletion of an
950
 * entry, the next or previous entry is opened, whichever is found first.
951
952
953
954
 * Return 0 on success or 1 on error.
 */
int close_open_file(void)
{
955
    openfilestruct *tmp;
956
957
958
959

    if (!open_files)
	return 1;

Chris Allegretta's avatar
Chris Allegretta committed
960
961
962
    /* 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 */
963
    open_files->fileage = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
964
    open_files->filebot = filebot;
Chris Allegretta's avatar
Chris Allegretta committed
965

966
    tmp = open_files;
967
968
    if (open_nextfile(1)) {
	if (open_prevfile(1))
969
970
971
	    return 1;
    }

972
973
    unlink_opennode(tmp);
    delete_opennode(tmp);
974
975
976
977
978

    shortcut_init(0);
    display_main_list();
    return 0;
}
979
#endif /* MULTIBUFFER */
980

981
#if !defined(DISABLE_SPELLER) || !defined(DISABLE_OPERATINGDIR)
982
/*
983
984
985
986
987
988
 * 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).
989
 */
990
char *get_full_path(const char *origpath)
991
{
992
993
994
    char *newpath = NULL, *last_slash, *d_here, *d_there, *d_there_file, tmp;
    int path_only, last_slash_index;
    struct stat fileinfo;
995
    char *expanded_origpath;
996

997
    /* first, get the current directory, and tack a slash onto the end of
998
       it, unless it turns out to be "/", in which case leave it alone */
999
1000
1001
1002
1003
1004
1005
1006
1007
1008

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

    if (d_here) {

	align(&d_here);
1009
1010
1011
1012
	if (strcmp(d_here, "/")) {
	    d_here = nrealloc(d_here, strlen(d_here) + 2);
	    strcat(d_here, "/");
	}
1013
1014
1015
1016
1017

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

1020
	expanded_origpath = real_dir_from_tilde(origpath);
1021
	/* save the value of origpath in both d_there and d_there_file */
1022
1023
1024
	d_there = mallocstrcpy(NULL, expanded_origpath);
	d_there_file = mallocstrcpy(NULL, expanded_origpath);
	free(expanded_origpath);
1025

1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
	/* 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 != '/') {
		d_there = nrealloc(d_there, strlen(d_there) + 2);
		strcat(d_there, "/");
		d_there_file = nrealloc(d_there_file, strlen(d_there_file) + 2);
		strcat(d_there_file, "/");
	    }
	}

1039
1040
1041
1042
1043
	/* 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 */
1044
1045
	if (!last_slash)
	    d_there = mallocstrcpy(d_there, d_here);
1046
	else {
1047
1048
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1049
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1050
	    null_at(&d_there, last_slash_index + 1);
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060

	    /* and remove all non-file elements from d_there_file (i. e.
	       everything before and including the last slash); if we
	        have a path but no filename, don't do anything */
	    if (!path_only) {
		last_slash = strrchr(d_there_file, '/');
		last_slash++;
		strcpy(d_there_file, last_slash);
		align(&d_there_file);
	    }
1061
1062
1063

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {
1064
1065
1066
		/* 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 */
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076

		free(d_there);

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

		align(&d_there);
1077
		if (d_there) {
1078
1079
1080
1081
1082
1083
1084

		    /* add a slash to d_there, unless it's "/", in which
		       case we don't need it */
		    if (strcmp(d_there, "/")) {
			d_there = nrealloc(d_there, strlen(d_there) + 2);
			strcat(d_there, "/");
		    }
1085
1086
1087
		}
		else
		    return NULL;
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
	    }

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

1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
	/* 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);
	}
1109
1110
1111
1112
1113
1114
1115
1116
1117

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

    return newpath;
}
1118
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1119
1120
1121

#ifndef DISABLE_SPELLER
/*
1122
1123
1124
 * 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.
1125
 */
1126
char *check_writable_directory(const char *path)
1127
{
1128
    char *full_path = get_full_path(path);
1129
    int writable;
1130
1131
    struct stat fileinfo;

1132
1133
    /* if get_full_path() failed, return NULL */
    if (!full_path)
1134
	return NULL;
1135
1136
1137

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

    /* if the full path doesn't end in a slash (meaning get_full_path()
1141
1142
1143
1144
       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);
1145
	return NULL;
1146
    }
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156

    /* 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
1157
1158
1159
1160
1161
1162
1163
 * 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.
1164
 */
1165
1166
char *safe_tempnam(const char *dirname, const char *filename_prefix)
{
1167
1168
    char *full_tempdir = NULL;
    const char *TMPDIR_env;
1169
    int filedesc;
1170

1171
1172
1173
1174
1175
1176
      /* if $TMPDIR is set and non-empty, set tempdir to it, run it through
         get_full_path(), and save the result in full_tempdir; otherwise,
         leave full_tempdir set to to NULL */
    TMPDIR_env = getenv("TMPDIR");
    if (TMPDIR_env && TMPDIR_env[0] != '\0')
	full_tempdir = check_writable_directory(TMPDIR_env);
1177

1178
1179
1180
1181
1182
    /* 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 */
    if (!full_tempdir && dirname)
	full_tempdir = check_writable_directory(dirname);
1183
1184
1185

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
1186
1187
    if (!full_tempdir)
	full_tempdir = check_writable_directory(P_tmpdir);
1188
1189
1190
1191
1192
1193
1194

    /* if P_tmpdir didn't work, use /tmp instead */
    if (!full_tempdir) {
	full_tempdir = charalloc(6);
	strcpy(full_tempdir, "/tmp/");
    }

1195
    full_tempdir = nrealloc(full_tempdir, strlen(full_tempdir) + 12);
1196
1197

    /* like tempnam(), use only the first 5 characters of the prefix */
1198
1199
1200
1201
1202
1203
1204
1205
    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) {
1206
	close(filedesc);
1207
1208
	unlink(full_tempdir);
	return full_tempdir;
1209
    }
1210
1211
1212

    free(full_tempdir);
    return NULL;
1213
1214
}
#endif /* !DISABLE_SPELLER */
1215
1216

#ifndef DISABLE_OPERATINGDIR
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
/* Initialize full_operating_dir based on operating_dir. */
void init_operating_dir(void)
{
    assert(full_operating_dir == NULL);

    if (!operating_dir)
	return;
    full_operating_dir = get_full_path(operating_dir);

    /* If get_full_path() failed or the operating directory is
       inaccessible, unset operating_dir. */
    if (!full_operating_dir || chdir(full_operating_dir) == -1) {
	free(full_operating_dir);
	full_operating_dir = NULL;
	free(operating_dir);
	operating_dir = NULL;
    }
}

1236
1237
1238
1239
1240
1241
/*
 * Check to see if we're inside the operating directory.  Return 0 if we
 * are, or 1 otherwise.  If allow_tabcomp is nonzero, allow incomplete
 * names that would be matches for the operating directory, so that tab
 * completion will work.
 */
1242
int check_operating_dir(const char *currpath, int allow_tabcomp)
1243
{
1244
1245
1246
    /* The char *full_operating_dir is global for mem cleanup, and
       therefore we only need to get it the first time this function
       is called; also, a relative operating directory path will
1247
1248
       only be handled properly if this is done */

1249
1250
1251
    char *fullpath;
    int retval = 0;
    const char *whereami1, *whereami2 = NULL;
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265

    /* if no operating directory is set, don't bother doing anything */
    if (!operating_dir)
	return 0;

    fullpath = get_full_path(currpath);
    if (!fullpath)
	return 1;

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

    /* if both searches failed, we're outside the operating directory */
1266
    /* otherwise */
1267
1268
1269
1270
1271
    /* 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 */
    if (whereami1 != fullpath && whereami2 != full_operating_dir)
1272
1273
	retval = 1;
    free(fullpath);	
1274
    /* otherwise, we're still inside it */
1275
    return retval;
1276
}
1277
1278
#endif

Chris Allegretta's avatar
Chris Allegretta committed
1279
1280
/*
 * Write a file out.  If tmp is nonzero, we set the umask to 0600,
1281
 * we don't set the global variable filename to its name, and don't
Chris Allegretta's avatar
Chris Allegretta committed
1282
1283
 * print out how many lines we wrote on the statusbar.
 * 
1284
 * tmp means we are writing a tmp file in a secure fashion.  We use
1285
 * it when spell checking or dumping the file on an error.
1286
 *
1287
1288
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1289
1290
 *
 * nonamechange means don't change the current filename, it is ignored
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1291
 * if tmp == 1 or if we're appending/prepending.
Chris Allegretta's avatar
Chris Allegretta committed
1292
 */
1293
int write_file(const char *name, int tmp, int append, int nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1294
{
1295
1296
1297
    int retval = -1;
	/* Instead of returning in this function, you should always
	 * merely set retval then goto cleanup_and_exit. */
Chris Allegretta's avatar
Chris Allegretta committed
1298
    long size, lineswritten = 0;
1299
    char *buf = NULL;
1300
    const filestruct *fileptr;
1301
1302
1303
    FILE *f;
    int fd;
    int mask = 0, realexists, anyexists;
1304
    struct stat st, lst;
1305
    char *realname = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
1306

Chris Allegretta's avatar
Chris Allegretta committed
1307
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1308
1309
1310
	statusbar(_("Cancelled"));
	return -1;
    }
Chris Allegretta's avatar
Chris Allegretta committed
1311
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1312
    fileptr = fileage;
1313

1314
#ifndef DISABLE_TABCOMP
1315
1316
1317
1318
    realname = real_dir_from_tilde(name);
#else
    realname = mallocstrcpy(realname, name);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1319

1320
#ifndef DISABLE_OPERATINGDIR
1321
1322
1323
1324
1325
    /* If we're writing a temporary file, we're probably going outside
       the operating directory, so skip the operating directory test. */
    if (!tmp && operating_dir && check_operating_dir(realname, 0)) {
	statusbar(_("Can't write outside of %s"), operating_dir);
	goto cleanup_and_exit;
1326
1327
1328
    }
#endif

1329
1330
    /* Save the state of file at the end of the symlink (if there is
       one). */
1331
1332
    realexists = stat(realname, &st);

1333
1334
1335
#ifndef NANO_SMALL
    /* We backup only if the backup toggle is set, the file isn't
       temporary, and the file already exists.  Furthermore, if we aren't
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1336
       appending, prepending, or writing a selection, we backup only if
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
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
       the file has not been modified by someone else since nano opened
       it. */
    if (ISSET(BACKUP_FILE) && !tmp && realexists == 0 &&
	    (append || ISSET(MARK_ISSET) ||
		originalfilestat.st_mtime == st.st_mtime)) {
	FILE *backup_file;
	char *backupname = NULL;
	char backupbuf[COPYFILEBLOCKSIZE];
	size_t bytesread;
	struct utimbuf filetime;

	/* save the original file's access and modification times */
	filetime.actime = originalfilestat.st_atime;
	filetime.modtime = originalfilestat.st_mtime;

	/* open the original file to copy to the backup */
	f = fopen(realname, "rb");
	if (!f) {
	    statusbar(_("Could not read %s for backup: %s"), realname,
		strerror(errno));
	    return -1;
	}

	backupname = charalloc(strlen(realname) + 2);
	sprintf(backupname, "%s~", realname);

	/* get a file descriptor for the destination backup file */
	backup_file = fopen(backupname, "wb");
	if (!backup_file) {
	    statusbar(_("Couldn't write backup: %s"), strerror(errno));
	    free(backupname);
	    return -1;
	}

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

	/* copy the file */
	while ((bytesread = fread(backupbuf, sizeof(char),
		COPYFILEBLOCKSIZE, f)) > 0)
	    if (fwrite(backupbuf, sizeof(char), bytesread, backup_file) <= 0)
		break;
	fclose(backup_file);
	fclose(f);

	if (chmod(backupname, originalfilestat.st_mode) == -1)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1384
	    statusbar(_("Could not set permissions %o on backup %s: %s"),
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
			originalfilestat.st_mode, backupname,
			strerror(errno));

	if (chown(backupname, originalfilestat.st_uid,
		originalfilestat.st_gid) == -1)
	    statusbar(_("Could not set owner %d/group %d on backup %s: %s"),
			originalfilestat.st_uid, originalfilestat.st_gid,
			backupname, strerror(errno));

	if (utime(backupname, &filetime) == -1)
	    statusbar(_("Could not set access/modification time on backup %s: %s"),
			backupname, strerror(errno));

	free(backupname);
    }
#endif

1402
1403
    /* Stat the link itself for the check... */
    anyexists = lstat(realname, &lst);
1404

1405
1406
    /* New case: if the file exists, just give up */
    if (tmp && anyexists != -1)
1407
	goto cleanup_and_exit;
1408
    /* NOTE: If you change this statement, you MUST CHANGE the if 
1409
1410
1411
       statement below (that says:
		if (realexists == -1 || tmp || (!ISSET(FOLLOW_SYMLINKS) &&
		S_ISLNK(lst.st_mode))) {
1412
       to reflect whether or not to link/unlink/rename the file */
1413
1414
    else if (append != 2 && (ISSET(FOLLOW_SYMLINKS) || !S_ISLNK(lst.st_mode) 
		|| tmp)) {
Chris Allegretta's avatar
Chris Allegretta committed
1415
	/* Use O_EXCL if tmp == 1.  This is now copied from joe, because
1416
	   wiggy says so *shrug*. */
1417
	if (append)
1418
	    fd = open(realname, O_WRONLY | O_CREAT | O_APPEND, (S_IRUSR|S_IWUSR));
1419
	else if (tmp)
1420
1421
1422
	    fd = open(realname, O_WRONLY | O_CREAT | O_EXCL, (S_IRUSR|S_IWUSR));
	else
	    fd = open(realname, O_WRONLY | O_CREAT | O_TRUNC, (S_IRUSR|S_IWUSR));
1423
1424

	/* First, just give up if we couldn't even open the file */
1425
	if (fd == -1) {
1426
	    if (!tmp && ISSET(TEMP_OPT)) {
Chris Allegretta's avatar
Chris Allegretta committed
1427
		UNSET(TEMP_OPT);
1428
1429
1430
1431
1432
		retval = do_writeout(filename, 1, 0);
	    } else
		statusbar(_("Could not open file for writing: %s"),
			strerror(errno));
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1433
	}
1434

Chris Allegretta's avatar
Chris Allegretta committed
1435
1436
1437
    }
    /* Don't follow symlink.  Create new file. */
    else {
1438
	buf = charalloc(strlen(realname) + 8);
1439
	strcpy(buf, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1440
1441
	strcat(buf, ".XXXXXX");
	if ((fd = mkstemp(buf)) == -1) {
Chris Allegretta's avatar
Chris Allegretta committed
1442
1443
	    if (ISSET(TEMP_OPT)) {
		UNSET(TEMP_OPT);
1444
1445
1446
1447
1448
		retval = do_writeout(filename, 1, 0);
	    } else
		statusbar(_("Could not open file for writing: %s"),
			strerror(errno));
	    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1449
1450
1451
	}
    }

Chris Allegretta's avatar
Chris Allegretta committed
1452
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
1453
    dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
1454
#endif
1455

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1456
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1457
    if (!f) {
1458
1459
	statusbar(_("Could not open file for writing: %s"), strerror(errno));
	goto cleanup_and_exit;
1460
1461
    }

Chris Allegretta's avatar
Chris Allegretta committed
1462
    while (fileptr != NULL && fileptr->next != NULL) {
1463
	int data_len;
1464

Robert Siemborski's avatar
Robert Siemborski committed
1465
	/* Next line is so we discount the "magic line" */
1466
1467
	if (filebot == fileptr && fileptr->data[0] == '\0')
	    break;
Robert Siemborski's avatar
Robert Siemborski committed
1468

1469
	data_len = strlen(fileptr->data);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1470
1471
1472
1473

	/* newlines to nulls, just before we write to disk */
	sunder(fileptr->data);

1474
	size = fwrite(fileptr->data, 1, data_len, f);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1475
1476
1477
1478

	/* nulls to newlines; data_len is the string's real length here */
	unsunder(fileptr->data, data_len);

1479
	if (size < data_len) {
Chris Allegretta's avatar
Chris Allegretta committed
1480
1481
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
1482
	    fclose(f);
1483
	    goto cleanup_and_exit;
1484
	}
Chris Allegretta's avatar
Chris Allegretta committed
1485
#ifdef DEBUG
1486
	else
Chris Allegretta's avatar
Chris Allegretta committed
1487
1488
	    fprintf(stderr, _("Wrote >%s\n"), fileptr->data);
#endif
1489
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1490
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1491
	    putc('\r', f);
Chris Allegretta's avatar
Chris Allegretta committed
1492
1493

	if (!ISSET(MAC_FILE))
1494
#endif
1495
	    putc('\n', f);
Chris Allegretta's avatar
Chris Allegretta committed
1496
1497
1498
1499
1500
1501

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

    if (fileptr != NULL) {
1502
	int data_len = strlen(fileptr->data);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1503
1504
1505
1506

	/* newlines to nulls, just before we write to disk */
	sunder(fileptr->data);

1507
	size = fwrite(fileptr->data, 1, data_len, f);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1508
1509
1510
1511

	/* nulls to newlines; data_len is the string's real length here */
	unsunder(fileptr->data, data_len);

1512
	if (size < data_len) {
Chris Allegretta's avatar
Chris Allegretta committed
1513
1514
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
1515
	    goto cleanup_and_exit;
1516
	} else if (data_len > 0) {
1517
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1518
	    if (ISSET(DOS_FILE) || ISSET(MAC_FILE)) {
1519
		if (putc('\r', f) == EOF) {
1520
1521
		    statusbar(_("Could not open file for writing: %s"),
			  strerror(errno));
1522
		    fclose(f);
1523
		    goto cleanup_and_exit;
1524
		}
1525
		lineswritten++;
1526
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1527
1528

	    if (!ISSET(MAC_FILE))
1529
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1530
	    {
1531
		if (putc('\n', f) == EOF) {
Chris Allegretta's avatar
Chris Allegretta committed
1532
		    statusbar(_("Could not open file for writing: %s"),
Chris Allegretta's avatar
Chris Allegretta committed
1533
			  strerror(errno));
1534
		    fclose(f);
1535
		    goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1536
		}
1537
		lineswritten++;
Chris Allegretta's avatar
Chris Allegretta committed
1538
1539
1540
1541
	    }
	}
    }

1542
    if (fclose(f)) {
1543
	statusbar(_("Could not close %s: %s"), realname, strerror(errno));
Chris Allegretta's avatar
Chris Allegretta committed
1544
	unlink(buf);
1545
	goto cleanup_and_exit;
Chris Allegretta's avatar
Chris Allegretta committed
1546
1547
    }

1548
1549
    /* if we're prepending, open the real file, and append it here */
    if (append == 2) {
1550
1551
1552
	int fd_source, fd_dest;
	FILE *f_source, *f_dest;
	int prechar;
1553
1554

	if ((fd_dest = open(buf, O_WRONLY | O_APPEND, (S_IRUSR|S_IWUSR))) == -1) {
1555
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
1556
	    goto cleanup_and_exit;
1557
	}
1558
1559
1560
	f_dest = fdopen(fd_dest, "wb");
	if (!f_dest) {
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
1561
	    close(fd_dest);
1562
	    goto cleanup_and_exit;
1563
	}
1564
	if ((fd_source = open(realname, O_RDONLY | O_CREAT)) == -1) {
1565
	    statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
1566
	    fclose(f_dest);
1567
	    goto cleanup_and_exit;
1568
1569
1570
1571
1572
1573
	}
	f_source = fdopen(fd_source, "rb");
	if (!f_source) {
	    statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
	    fclose(f_dest);
	    close(fd_source);
1574
	    goto cleanup_and_exit;
1575
1576
	}

1577
1578
1579
1580
1581
1582
        /* Doing this in blocks is an exercise left to some other reader. */
	while ((prechar = getc(f_source)) != EOF) {
	    if (putc(prechar, f_dest) == EOF) {
		statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
		fclose(f_source);
		fclose(f_dest);
1583
		goto cleanup_and_exit;
1584
1585
	    }
	}
1586

1587
1588
1589
1590
	if (ferror(f_source)) {
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
	    fclose(f_source);
	    fclose(f_dest);
1591
	    goto cleanup_and_exit;
1592
1593
1594
1595
	}
	    
	fclose(f_source);
	fclose(f_dest);
1596
1597
    }

1598
1599
    if (realexists == -1 || tmp ||
	(!ISSET(FOLLOW_SYMLINKS) && S_ISLNK(lst.st_mode))) {
Chris Allegretta's avatar
Chris Allegretta committed
1600

1601
	/* Use default umask as file permissions if file is a new file. */
1602
1603
	mask = umask(0);
	umask(mask);
Chris Allegretta's avatar
Chris Allegretta committed
1604

1605
	if (tmp)	/* We don't want anyone reading our temporary file! */
1606
1607
1608
1609
	    mask = 0600;
	else
	    mask = 0666 & ~mask;
    } else
Chris Allegretta's avatar
Chris Allegretta committed
1610
	/* Use permissions from file we are overwriting. */
1611
1612
	mask = st.st_mode;

1613
1614
    if (append == 2 || 
		(!tmp && (!ISSET(FOLLOW_SYMLINKS) && S_ISLNK(lst.st_mode)))) {
1615
1616
	if (unlink(realname) == -1) {
	    if (errno != ENOENT) {
1617
		statusbar(_("Could not open %s for writing: %s"),
1618
			  realname, strerror(errno));
1619
		unlink(buf);
1620
		goto cleanup_and_exit;
1621
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1622
	}
1623
1624
1625
1626
1627
1628
	if (link(buf, realname) != -1)
	    unlink(buf);
	else if (errno != EPERM) {
	    statusbar(_("Could not open %s for writing: %s"),
		      name, strerror(errno));
	    unlink(buf);
1629
	    goto cleanup_and_exit;
1630
1631
1632
1633
	} else if (rename(buf, realname) == -1) {	/* Try a rename?? */
	    statusbar(_("Could not open %s for writing: %s"),
		      realname, strerror(errno));
	    unlink(buf);
1634
	    goto cleanup_and_exit;
1635
	}
Chris Allegretta's avatar
Chris Allegretta committed
1636
    }
1637
1638
1639
1640
    if (chmod(realname, mask) == -1)
	statusbar(_("Could not set permissions %o on %s: %s"),
		  mask, realname, strerror(errno));

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1641
    if (!tmp && !append) {
Chris Allegretta's avatar
Chris Allegretta committed
1642
	if (!nonamechange)
1643
1644
	    filename = mallocstrcpy(filename, realname);

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

    retval = 1;

  cleanup_and_exit:
    free(realname);
    free(buf);
    return retval;
Chris Allegretta's avatar
Chris Allegretta committed
1661
1662
}

1663
int do_writeout(const char *path, int exiting, int append)
Chris Allegretta's avatar
Chris Allegretta committed
1664
1665
{
    int i = 0;
1666
1667
1668
#ifdef NANO_EXTRA
    static int did_cred = 0;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1669

1670
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1671
    currshortcut = writefile_list;
1672
1673
#endif

1674
    answer = mallocstrcpy(answer, path);
Chris Allegretta's avatar
Chris Allegretta committed
1675

1676
    if (exiting && ISSET(TEMP_OPT)) {
1677
	if (filename[0]) {
1678
	    i = write_file(answer, 0, 0, 0);
1679
1680
	    display_main_list();
	    return i;
1681
	} else {
Chris Allegretta's avatar
Chris Allegretta committed
1682
1683
1684
1685
1686
	    UNSET(TEMP_OPT);
	    do_exit();

	    /* They cancelled, abort quit */
	    return -1;
1687
	}
Chris Allegretta's avatar
Chris Allegretta committed
1688
1689
1690
    }

    while (1) {
1691
#ifndef NANO_SMALL
1692
1693
	const char *formatstr, *backupstr;

1694
1695
1696
1697
1698
1699
1700
	if (ISSET(MAC_FILE))
	   formatstr = _(" [Mac Format]");
	else if (ISSET(DOS_FILE))
	   formatstr = _(" [DOS Format]");
	else
	   formatstr = "";

1701
1702
1703
1704
1705
	if (ISSET(BACKUP_FILE))
	   backupstr = _(" [Backup]");
	else
	   backupstr = "";

1706
	/* Be nice to the translation folks */
1707
	if (ISSET(MARK_ISSET) && !exiting) {
1708
1709
	    if (append == 2)
		i = statusq(1, writefile_list, "",
1710
		    "%s%s%s", _("Prepend Selection to File"), formatstr, backupstr);
1711
	    else if (append)
1712
		i = statusq(1, writefile_list, "",
1713
		    "%s%s%s", _("Append Selection to File"), formatstr, backupstr);
1714
	    else
1715
		i = statusq(1, writefile_list, "",
1716
1717
		    "%s%s%s", _("Write Selection to File"), formatstr, backupstr);
	} else {
1718
1719
	    if (append == 2)
		i = statusq(1, writefile_list, answer,
1720
		    "%s%s%s", _("File Name to Prepend to"), formatstr, backupstr);
1721
	    else if (append)
1722
		i = statusq(1, writefile_list, answer,
1723
		    "%s%s%s", _("File Name to Append to"), formatstr, backupstr);
1724
	    else
1725
		i = statusq(1, writefile_list, answer,
1726
		    "%s%s%s", _("File Name to Write"), formatstr, backupstr);
1727
	}
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
#else
	if (append == 2)
	    i = statusq(1, writefile_list, answer,
		"%s", _("File Name to Prepend to"));
	else if (append)
	    i = statusq(1, writefile_list, answer,
		"%s", _("File Name to Append to"));
	else
	    i = statusq(1, writefile_list, answer,
		"%s", _("File Name to Write"));
#endif /* !NANO_SMALL */
Chris Allegretta's avatar
Chris Allegretta committed
1739

1740
1741
1742
1743
1744
	if (i == -1) {
	    statusbar(_("Cancelled"));
	    display_main_list();
	    return 0;
	}
Chris Allegretta's avatar
Chris Allegretta committed
1745

1746
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1747
	if (i == NANO_TOFILES_KEY) {
1748
	    char *tmp = do_browse_from(answer);
1749

1750
	    currshortcut = writefile_list;
1751
1752
1753
1754
	    if (tmp == NULL)
		continue;
	    free(answer);
	    answer = tmp;
1755
	} else
1756
#endif /* !DISABLE_BROWSER */
Chris Allegretta's avatar
Chris Allegretta committed
1757
#ifndef NANO_SMALL
1758
1759
1760
	if (i == TOGGLE_DOS_KEY) {
	    UNSET(MAC_FILE);
	    TOGGLE(DOS_FILE);
1761
	    continue;
1762
1763
1764
	} else if (i == TOGGLE_MAC_KEY) {
	    UNSET(DOS_FILE);
	    TOGGLE(MAC_FILE);
1765
	    continue;
1766
1767
	} else if (i == TOGGLE_BACKUP_KEY) {
	    TOGGLE(BACKUP_FILE);
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
	    continue;
	} else
#endif /* ! NANO_SMALL */
	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
1778

Chris Allegretta's avatar
Chris Allegretta committed
1779
#ifdef DEBUG
1780
	fprintf(stderr, _("filename is %s\n"), answer);
Chris Allegretta's avatar
Chris Allegretta committed
1781
#endif
1782
1783

#ifdef NANO_EXTRA
1784
	if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy")
1785
		&& !did_cred) {
1786
1787
1788
1789
	    do_credits();
	    did_cred = 1;
	    return -1;
	}
1790
#endif
1791
1792
	if (!append && strcmp(answer, filename)) {
	    struct stat st;
Chris Allegretta's avatar
Chris Allegretta committed
1793

1794
1795
1796
1797
1798
	    if (!stat(answer, &st)) {
		i = do_yesno(0, 0, _("File exists, OVERWRITE ?"));

		if (i == 0 || i == -1)
		    continue;
Chris Allegretta's avatar
Chris Allegretta committed
1799
	    }
1800
	}
Chris Allegretta's avatar
Chris Allegretta committed
1801

1802
#ifndef NANO_SMALL
1803
1804
1805
	/* Here's where we allow the selected text to be written to 
	   a separate file. */
	if (ISSET(MARK_ISSET) && !exiting) {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1806
	    filestruct *fileagebak = fileage;
1807
1808
	    filestruct *filebotbak = filebot;
	    filestruct *cutback = cutbuffer;
1809
1810
	    int oldmod = ISSET(MODIFIED);
		/* write_file() unsets the MODIFIED flag. */
1811

1812
	    cutbuffer = NULL;
1813

1814
1815
1816
	    /* Put the marked text in the cutbuffer without changing
	       the open file. */
	    cut_marked_segment(current, current_x, mark_beginbuf,
1817
1818
1819
				mark_beginx, 0);

	    fileage = cutbuffer;
1820
	    filebot = get_cutbottom();
1821
	    i = write_file(answer, 0, append, 1);
1822
1823

	    /* Now restore everything */
1824
	    free_filestruct(cutbuffer);
1825
1826
1827
1828
1829
1830
	    fileage = fileagebak;
	    filebot = filebotbak;
	    cutbuffer = cutback;
	    if (oldmod)
		set_modified();
	} else
1831
#endif /* !NANO_SMALL */
1832
	    i = write_file(answer, 0, append, 0);
1833

1834
#ifdef ENABLE_MULTIBUFFER
1835
1836
1837
1838
	/* If we're not about to exit, update the current entry in
	   the open_files structure. */
	if (!exiting)
	    add_open_file(1);
1839
#endif
1840
1841
1842
	display_main_list();
	return i;
    } /* while (1) */
Chris Allegretta's avatar
Chris Allegretta committed
1843
1844
1845
1846
}

int do_writeout_void(void)
{
1847
    return do_writeout(filename, 0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
1848
}
Chris Allegretta's avatar
Chris Allegretta committed
1849

1850
#ifndef DISABLE_TABCOMP
1851
1852

/* Return a malloc()ed string containing the actual directory, used
1853
1854
 * to convert ~user and ~/ notation... */
char *real_dir_from_tilde(const char *buf)
1855
{
Chris Allegretta's avatar
Chris Allegretta committed
1856
    char *dirtmp = NULL;
1857

1858
    if (buf[0] == '~') {
1859
1860
1861
1862
1863
1864
1865
1866
	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++)
	    ;

	if (i == 1) {
Chris Allegretta's avatar
Chris Allegretta committed
1867
1868
1869
	    /* Determine home directory using getpwent(), don't rely on
	       $HOME */
	    uid_t euid = geteuid();
1870

Chris Allegretta's avatar
Chris Allegretta committed
1871
1872
1873
	    do {
		userdata = getpwent();
	    } while (userdata != NULL && userdata->pw_uid != euid);
1874
1875
1876
1877
1878
	} else {
	    do {
		userdata = getpwent();
	    } while (userdata != NULL &&
			strncmp(userdata->pw_name, buf + 1, i - 1));
Chris Allegretta's avatar
Chris Allegretta committed
1879
1880
	}
	endpwent();
1881

Chris Allegretta's avatar
Chris Allegretta committed
1882
	if (userdata != NULL) {  /* User found */
1883
	    dirtmp = charalloc(strlen(userdata->pw_dir) + strlen(buf + i) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1884
	    sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);
1885
	}
1886
    }
1887

1888
1889
1890
    if (dirtmp == NULL)
	dirtmp = mallocstrcpy(dirtmp, buf);

1891
    return dirtmp;
1892
1893
}

1894
1895
1896
/* 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
1897
int append_slash_if_dir(char *buf, int *lastwastab, int *place)
1898
{
1899
    char *dirptr = real_dir_from_tilde(buf);
1900
    struct stat fileinfo;
1901
    int ret = 0;
1902

1903
    assert(dirptr != buf);
1904

1905
    if (stat(dirptr, &fileinfo) != -1 && S_ISDIR(fileinfo.st_mode)) {
1906
	strncat(buf, "/", 1);
1907
	(*place)++;
1908
	/* now we start over again with # of tabs so far */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1909
	*lastwastab = 0;
1910
	ret = 1;
1911
1912
    }

1913
    free(dirptr);
1914
    return ret;
1915
}
Chris Allegretta's avatar
Chris Allegretta committed
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937

/*
 * These functions (username_tab_completion, cwd_tab_completion, and
 * input_tab were taken from busybox 0.46 (cmdedit.c).  Here is the notice
 * from that file:
 *
 * 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)
{
1938
    char **matches = (char **) NULL;
1939
    char *matchline = NULL;
1940
    struct passwd *userdata;
1941

1942
    *num_matches = 0;
1943
    matches = nmalloc(BUFSIZ * sizeof(char *));
1944

1945
    strcat(buf, "*");
1946

1947
    while ((userdata = getpwent()) != NULL) {
1948

1949
	if (check_wildcard_match(userdata->pw_name, &buf[1]) == TRUE) {
1950
1951
1952
1953

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

1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
#ifndef DISABLE_OPERATINGDIR
	    /* ...unless the match exists outside the operating
               directory, in which case just go to the next match */

	    if (operating_dir) {
		if (check_operating_dir(userdata->pw_dir, 1))
		    continue;
	    }
#endif

1965
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
1966
1967
1968
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
	    ++*num_matches;
1969

1970
1971
1972
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
1973
	}
1974
1975
    }
    endpwent();
1976

Chris Allegretta's avatar
Chris Allegretta committed
1977
1978
1979
1980
1981
1982
1983
1984
    return (matches);
}

/* 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
1985
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
1986
1987
1988
1989
    char **matches = (char **) NULL;
    DIR *dir;
    struct dirent *next;

1990
    matches = nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
1991
1992
1993
1994

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

1995
    /* Okie, if there's a / in the buffer, strip out the directory part */
Chris Allegretta's avatar
Chris Allegretta committed
1996
    if (buf[0] != '\0' && strstr(buf, "/")) {
Chris Allegretta's avatar
Chris Allegretta committed
1997
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
1998
1999
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2000
	    tmp--;
2001

Chris Allegretta's avatar
Chris Allegretta committed
2002
2003
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2004
2005
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2006

Chris Allegretta's avatar
Chris Allegretta committed
2007
    } else {
2008
2009

#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2010
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
2011
#else
2012
	/* The better, but apparently segfault-causing way */
Chris Allegretta's avatar
Chris Allegretta committed
2013
	if ((dirname = getcwd(NULL, 0)) == NULL)
2014
#endif /* PATH_MAX */
Chris Allegretta's avatar
Chris Allegretta committed
2015
2016
2017
2018
2019
2020
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2021
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2022
2023
2024
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2025

Chris Allegretta's avatar
Chris Allegretta committed
2026
2027
2028
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2029
2030

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2031
    fprintf(stderr, "\nDir = %s\n", dirname);
2032
2033
2034
2035
2036
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2037
    dir = opendir(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2038
2039
2040
2041
2042
2043
2044
2045
2046
    if (!dir) {
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
	return (matches);
    }
    while ((next = readdir(dir)) != NULL) {

#ifdef DEBUG
2047
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2048
2049
2050
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
2051
2052
2053
2054

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2055
2056
2057
2058
2059
2060
2061
2062
2063

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

	    if (operating_dir) {
Chris Allegretta's avatar
Chris Allegretta committed
2064
2065
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
		if (check_operating_dir(tmp2, 1)) {
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2076
	    tmp2 = NULL;
2077
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2078
2079
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2080
	    ++*num_matches;
2081
2082
2083
2084

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

    return (matches);
}

2091
/* This function now has an arg which refers to how much the 
Chris Allegretta's avatar
Chris Allegretta committed
2092
2093
 * statusbar (place) should be advanced, i.e. the new cursor pos.
 */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2094
char *input_tab(char *buf, int place, int *lastwastab, int *newplace, int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2095
2096
{
    /* Do TAB completion */
2097
    static int num_matches = 0, match_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2098
    static char **matches = (char **) NULL;
2099
    int pos = place, i = 0, col = 0, editline = 0;
2100
    int longestname = 0, is_dir = 0;
2101
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2102

2103
2104
    *list = 0;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2105
    if (*lastwastab == FALSE) {
2106
	char *tmp, *copyto, *matchBuf;
Chris Allegretta's avatar
Chris Allegretta committed
2107

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2108
	*lastwastab = 1;
2109

Chris Allegretta's avatar
Chris Allegretta committed
2110
2111
	/* Make a local copy of the string -- up to the position of the
	   cursor */
2112
	matchBuf = (char *) nmalloc((strlen(buf) + 2) * sizeof(char));
2113
	memset(matchBuf, '\0', (strlen(buf) + 2));
2114

Chris Allegretta's avatar
Chris Allegretta committed
2115
2116
2117
2118
	strncpy(matchBuf, buf, place);
	tmp = matchBuf;

	/* skip any leading white space */
2119
	while (*tmp && isspace((int) *tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2120
2121
2122
2123
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2124
2125
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2126
2127
	    free(matches);
	    matches = (char **) NULL;
2128
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2129
2130
2131
2132
2133
	}

	/* 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
2134
2135
2136
2137
2138
2139
2140
	/* 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. */
	if (buf[0] == '~' && !strchr(tmp, '/')) {
	    buf = mallocstrcpy(buf, tmp);
2141
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2142
	}
2143
2144
2145
2146
2147
2148
	/* 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
2149
2150
2151
2152
2153
2154
2155
2156
2157

	/* Try to match everything in the current working directory that
	 * matches.  */
	if (!matches)
	    matches = cwd_tab_completion(tmp, &num_matches);

	/* Don't leak memory */
	free(matchBuf);

2158
2159
2160
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2161
	/* Did we find exactly one match? */
2162
	switch (num_matches) {
2163
	case 0:
2164
	    blank_edit();
2165
	    wrefresh(edit);
2166
2167
	    break;
	case 1:
2168
2169
2170

	    buf = nrealloc(buf, strlen(buf) + strlen(matches[0]) + 1);

Chris Allegretta's avatar
Chris Allegretta committed
2171
	    if (buf[0] != '\0' && strstr(buf, "/")) {
2172
2173
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2174
		tmp++;
2175
	    } else
2176
2177
		tmp = buf;

2178
	    if (!strcmp(tmp, matches[0]))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2179
		is_dir = append_slash_if_dir(buf, lastwastab, newplace);
2180
2181
2182

	    if (is_dir)
		break;
2183
2184

	    copyto = tmp;
2185
2186
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2187
2188
		tmp++;

2189
	    /* write out the matched name */
2190
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2191
2192
	    *newplace += strlen(matches[0]) - pos;

2193
2194
2195
2196
2197
2198
2199
	    /* 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;

2200
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2201
	    append_slash_if_dir(buf, lastwastab, newplace);
2202

2203
2204
	    break;
	default:
2205
	    /* Check to see if all matches share a beginning, and, if so,
2206
	       tack it onto buf and then beep */
2207

Chris Allegretta's avatar
Chris Allegretta committed
2208
	    if (buf[0] != '\0' && strstr(buf, "/")) {
2209
2210
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2211
		tmp++;
2212
	    } else
2213
2214
2215
		tmp = buf;

	    for (pos = 0; *tmp == matches[0][pos] && *tmp != 0 &&
2216
		 pos <= strlen(matches[0]); pos++)
2217
2218
		tmp++;

2219
2220
2221
2222
2223
2224
2225
2226
2227
	    while (1) {
		match_matches = 0;

		for (i = 0; i < num_matches; i++) {
		    if (matches[i][pos] == 0)
			break;
		    else if (matches[i][pos] == matches[0][pos])
			match_matches++;
		}
2228
2229
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2230
		    /* All the matches have the same character at pos+1,
2231
		       so paste it into buf... */
2232
		    buf = nrealloc(buf, strlen(buf) + 2);
2233
		    strncat(buf, matches[0] + pos, 1);
2234
		    *newplace += 1;
2235
		    pos++;
2236
		} else {
2237
2238
2239
2240
		    beep();
		    break;
		}
	    }
2241
	    break;
2242
	}
Chris Allegretta's avatar
Chris Allegretta committed
2243
2244
2245
2246
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2247
	if (matches && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2248
2249
2250
2251
2252

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

2253
	    editline = 0;
2254

2255
2256
2257
2258
2259
2260
2261
2262
	    /* 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;

2263
	    foo = charalloc(longestname + 5);
2264

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

2268
		/* make each filename shown be the same length as the longest
2269
		   filename, with two spaces at the end */
2270
2271
2272
2273
2274
2275
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2276
2277
		/* Disable el cursor */
		curs_set(0);
2278
2279
2280
2281
2282
2283
		/* 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 */
2284
		if (col > (COLS - longestname) && matches[i + 1] != NULL) {
2285
2286
		    editline++;
		    wmove(edit, editline, 0);
2287
2288
2289
2290
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2291
2292
2293
		    col = 0;
		}
	    }
2294
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2295
	    wrefresh(edit);
2296
	    *list = 1;
2297
2298
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2299
2300
    }

2301
2302
2303
2304
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
    if (*list == 0)
	edit_refresh();
2305
2306
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2307
}
2308
#endif /* !DISABLE_TABCOMP */
Chris Allegretta's avatar
Chris Allegretta committed
2309

2310
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
2311
2312

/* Return the stat of the file pointed to by path */
Chris Allegretta's avatar
Chris Allegretta committed
2313
2314
struct stat filestat(const char *path)
{
Chris Allegretta's avatar
Chris Allegretta committed
2315
2316
    struct stat st;

2317
    stat(path, &st);
Chris Allegretta's avatar
Chris Allegretta committed
2318
2319
2320
2321
    return st;
}

/* Our sort routine for file listings - sort directories before
2322
 * files, and then alphabetically. */ 
Chris Allegretta's avatar
Chris Allegretta committed
2323
2324
int diralphasort(const void *va, const void *vb)
{
2325
2326
2327
2328
    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
2329

2330
2331
2332
2333
    if (aisdir && !bisdir)
	return -1;
    if (!aisdir && bisdir)
	return 1;
2334
2335

#ifdef HAVE_STRCASECMP
2336
    return strcasecmp(a, b);
2337
#else
2338
    return strcmp(a, b);
2339
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2340
2341
}

2342
/* Free our malloc()ed memory */
Chris Allegretta's avatar
Chris Allegretta committed
2343
2344
void free_charptrarray(char **array, int len)
{
2345
2346
    for (; len > 0; len--)
	free(array[len - 1]);
Chris Allegretta's avatar
Chris Allegretta committed
2347
2348
2349
    free(array);
}

2350
2351
2352
/* Only print the last part of a path; isn't there a shell 
 * command for this? */
const char *tail(const char *foo)
Chris Allegretta's avatar
Chris Allegretta committed
2353
{
2354
    const char *tmp = foo + strlen(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2355
2356
2357
2358

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

2359
2360
    if (*tmp == '/')
	tmp++;
Chris Allegretta's avatar
Chris Allegretta committed
2361
2362
2363
2364

    return tmp;
}

2365
/* Strip one dir from the end of a string. */
Chris Allegretta's avatar
Chris Allegretta committed
2366
2367
void striponedir(char *foo)
{
2368
    char *tmp;
Chris Allegretta's avatar
Chris Allegretta committed
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381

    /* Don't strip the root dir */
    if (!strcmp(foo, "/"))
	return;

    tmp = foo + strlen(foo);
    if (*tmp == '/')
	tmp--;

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

    if (tmp != foo)
2382
2383
2384
2385
2386
	*tmp = '\0';
    else { /* SPK may need to make a 'default' path here */
        if (*tmp != '/')
	    *tmp = '.';
	*(tmp + 1) = '\0';
2387
    }
Chris Allegretta's avatar
Chris Allegretta committed
2388
2389
}

2390
/* Initialize the browser code, including the list of files in *path */
2391
char **browser_init(const char *path, int *longest, int *numents)
2392
2393
2394
{
    DIR *dir;
    struct dirent *next;
2395
    char **filelist;
2396
    int i = 0;
2397
    size_t path_len;
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415

    dir = opendir(path);
    if (!dir) 
	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;

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

2416
2417
2418
2419
    if (!strcmp(path, "/"))
	path = "";
    path_len = strlen(path);

2420
2421
2422
2423
    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;

2424
2425
	filelist[i] = charalloc(strlen(next->d_name) + path_len + 2);
	sprintf(filelist[i], "%s/%s", path, next->d_name);
2426
2427
2428
2429
2430
2431
2432
2433
2434
	i++;
    }

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

    return filelist;
}

Chris Allegretta's avatar
Chris Allegretta committed
2435
/* Our browser function.  inpath is the path to start browsing from */
2436
char *do_browser(const char *inpath)
Chris Allegretta's avatar
Chris Allegretta committed
2437
2438
2439
2440
2441
2442
{
    struct stat st;
    char *foo, *retval = NULL;
    static char *path = NULL;
    int numents = 0, i = 0, j = 0, kbinput = 0, longest = 0, abort = 0;
    int col = 0, selected = 0, editline = 0, width = 0, filecols = 0;
2443
    int lineno = 0, kb;
Chris Allegretta's avatar
Chris Allegretta committed
2444
    char **filelist = (char **) NULL;
2445
2446
2447
2448
2449
#ifndef DISABLE_MOUSE
#ifdef NCURSES_MOUSE_VERSION
    MEVENT mevent;
#endif
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2450

2451
2452
    assert(inpath != NULL);

Chris Allegretta's avatar
Chris Allegretta committed
2453
2454
2455
    /* 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
2456
2457
2458
2459
2460
    if (path != NULL && strcmp(path, inpath)) {
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2461
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2462
    if (path == NULL)
2463
	path = mallocstrcpy(NULL, inpath);
Chris Allegretta's avatar
Chris Allegretta committed
2464
2465

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

2468
    /* Sort the list by directory first, then alphabetically */
Chris Allegretta's avatar
Chris Allegretta committed
2469
2470
    qsort(filelist, numents, sizeof(char *), diralphasort);

2471
    kb = keypad_on(edit, 1);
Chris Allegretta's avatar
Chris Allegretta committed
2472
    titlebar(path);
2473
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2474
2475
2476
2477
2478
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2479
2480

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2481
    do {
Rocco Corsi's avatar
   
Rocco Corsi committed
2482
2483
2484
2485
	DIR *test_dir;

	blank_statusbar_refresh();

2486
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2487
	currshortcut = browser_list;
2488
2489
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2490
2491
 	editline = 0;
	col = 0;
2492
	    
2493
	/* Compute line number we're on now, so we don't divide by zero later */
2494
2495
2496
	lineno = selected;
	if (width != 0)
	    lineno /= width;
Chris Allegretta's avatar
Chris Allegretta committed
2497
2498

	switch (kbinput) {
2499

2500
#ifndef DISABLE_MOUSE
2501
#ifdef NCURSES_MOUSE_VERSION
2502
	case KEY_MOUSE:
2503
	    if (getmouse(&mevent) == ERR)
2504
		return retval;
2505
2506
2507
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
2508
	    if (wenclose(edit, mevent.y, mevent.x)) { 
2509
2510
2511
2512
		int selectedbackup = selected;

		mevent.y -= 2;

2513
2514
2515
2516
2517
2518
2519
2520
2521
		/* Longest is the width of each column.  There are two
		 * spaces between each column. */
		selected = (lineno / editwinrows) * editwinrows * width 
			+ mevent.y * width + mevent.x / (longest + 2);

		/* If they clicked beyond the end of a row, select the
		 * end of that row. */
		if (mevent.x > width * (longest + 2))
		    selected--;
2522
2523
2524

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2525
		if (selected > numents - 1)
2526
		    selected = numents - 1;
2527
		else if (selectedbackup == selected)
2528
2529
2530
2531
		    ungetch('s');	/* Unget the 'select' key */
	    } else	/* Must be clicking a shortcut */
		do_mouse();

2532
2533
2534
            break;
#endif
#endif
2535
	case NANO_UP_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2536
2537
2538
2539
2540
	case KEY_UP:
	case 'u':
	    if (selected - width >= 0)
		selected -= width;
	    break;
2541
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2542
	case KEY_LEFT:
2543
2544
	case NANO_BACKSPACE_KEY:
	case 127:
Chris Allegretta's avatar
Chris Allegretta committed
2545
2546
2547
2548
2549
	case 'l':
	    if (selected > 0)
		selected--;
	    break;
	case KEY_DOWN:
2550
	case NANO_DOWN_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2551
2552
2553
2554
2555
	case 'd':
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
	case KEY_RIGHT:
2556
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2557
2558
2559
2560
2561
	case 'r':
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2562
	case NANO_PREVPAGE_FKEY:
Chris Allegretta's avatar
Chris Allegretta committed
2563
	case KEY_PPAGE:
2564
	case '-':
2565
	    if (selected >= (editwinrows + lineno % editwinrows) * width)
2566
		selected -= (editwinrows + lineno % editwinrows) * width; 
Chris Allegretta's avatar
Chris Allegretta committed
2567
2568
2569
2570
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2571
	case NANO_NEXTPAGE_FKEY:
Chris Allegretta's avatar
Chris Allegretta committed
2572
	case KEY_NPAGE:	
2573
	case ' ':
2574
2575
	    selected += (editwinrows - lineno % editwinrows) * width;
	    if (selected >= numents)
Chris Allegretta's avatar
Chris Allegretta committed
2576
2577
		selected = numents - 1;
	    break;
2578
2579
2580
2581
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
	     do_help();
	     break;
Chris Allegretta's avatar
Chris Allegretta committed
2582
	case KEY_ENTER:
2583
	case NANO_ENTER_KEY:
2584
2585
	case 's': /* More Pico compatibility */
	case 'S':
Chris Allegretta's avatar
Chris Allegretta committed
2586
	    /* You can't cd up from / */
Rocco Corsi's avatar
   
Rocco Corsi committed
2587
	    if (!strcmp(filelist[selected], "/..") && !strcmp(path, "/")) {
Chris Allegretta's avatar
Chris Allegretta committed
2588
		statusbar(_("Can't move up a directory"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2589
2590
2591
2592
		break;
	    }

	    path = mallocstrcpy(path, filelist[selected]);
Chris Allegretta's avatar
Chris Allegretta committed
2593

2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
#ifndef DISABLE_OPERATINGDIR
	    /* Note: The case of the user's being completely outside the
	       operating directory is handled elsewhere, before this
	       point */
	    if (operating_dir) {
		if (check_operating_dir(path, 0)) {
		    statusbar(_("Can't visit parent in restricted mode"));
		    beep();
		    break;
		}
	    }
#endif

2607
2608
2609
	    /* SPK for '.' path, get the current path via getcwd */
	    if (!strcmp(path, "./..")) {
		free(path);
Chris Allegretta's avatar
Chris Allegretta committed
2610
2611
2612
#ifdef PATH_MAX
		path = getcwd(NULL, PATH_MAX + 1);
#else
2613
		path = getcwd(NULL, 0);
Chris Allegretta's avatar
Chris Allegretta committed
2614
#endif
2615
2616
2617
2618
2619
2620
2621
		striponedir(path);		    
		align(&path);
		free_charptrarray(filelist, numents);
		free(foo);
		return do_browser(path);
	    }

Chris Allegretta's avatar
Chris Allegretta committed
2622
2623
	    st = filestat(path);
	    if (S_ISDIR(st.st_mode)) {
Rocco Corsi's avatar
   
Rocco Corsi committed
2624
		if ((test_dir = opendir(path)) == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2625
		    /* We can't open this dir for some reason.  Complain */
Chris Allegretta's avatar
Chris Allegretta committed
2626
		    statusbar(_("Can't open \"%s\": %s"), path, strerror(errno));
Rocco Corsi's avatar
   
Rocco Corsi committed
2627
		    striponedir(path);
Chris Allegretta's avatar
Chris Allegretta committed
2628
2629
		    align(&path);
		    break;
Chris Allegretta's avatar
Chris Allegretta committed
2630
		} 
Rocco Corsi's avatar
   
Rocco Corsi committed
2631
		closedir(test_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2632
2633

		if (!strcmp("..", tail(path))) {
Chris Allegretta's avatar
Chris Allegretta committed
2634
2635
		    /* They want to go up a level, so strip off .. and the
			current dir */
Chris Allegretta's avatar
Chris Allegretta committed
2636
2637
2638
2639
		    striponedir(path);
		    striponedir(path);
		    align(&path);
		}
Chris Allegretta's avatar
Chris Allegretta committed
2640
2641

		/* Start over again with the new path value */
2642
2643
		free_charptrarray(filelist, numents);
		free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2644
2645
		return do_browser(path);
	    } else {
2646
		retval = mallocstrcpy(retval, path);
Chris Allegretta's avatar
Chris Allegretta committed
2647
2648
2649
		abort = 1;
	    }
	    break;
Rocco Corsi's avatar
   
Rocco Corsi committed
2650
2651
2652
2653
2654
2655
	/* Goto a specific directory */
	case 'g':	/* Pico compatibility */
	case 'G':
	case NANO_GOTO_KEY:

	    curs_set(1);
2656
2657
	    j = statusq(0, gotodir_list, "", _("Goto Directory"));
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2658
2659
	    curs_set(0);

2660
2661
2662
2663
2664
2665
2666
2667
2668
#ifndef DISABLE_OPERATINGDIR
	    if (operating_dir) {
		if (check_operating_dir(answer, 0)) {
		    statusbar(_("Can't go outside of %s in restricted mode"), operating_dir);
		    break;
		}
	    }
#endif

Rocco Corsi's avatar
   
Rocco Corsi committed
2669
2670
2671
2672
2673
2674
	    if (j < 0) {
		statusbar(_("Goto Cancelled"));
		break;
	    }

	    if (answer[0] != '/') {
2675
		char *saveanswer = mallocstrcpy(NULL, answer);
Rocco Corsi's avatar
   
Rocco Corsi committed
2676

2677
		answer = nrealloc(answer, strlen(path) + strlen(saveanswer) + 2);
Rocco Corsi's avatar
   
Rocco Corsi committed
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
		sprintf(answer, "%s/%s", path, saveanswer);
		free(saveanswer);
	    }

	    if ((test_dir = opendir(answer)) == NULL) {
		/* We can't open this dir for some reason.  Complain */
		statusbar(_("Can't open \"%s\": %s"), answer, strerror(errno));
		break;
	    } 
	    closedir(test_dir);

	    /* Start over again with the new path value */
	    path = mallocstrcpy(path, answer);
	    return do_browser(path);

Chris Allegretta's avatar
Chris Allegretta committed
2693
2694
2695
2696
2697
	/* Stuff we want to abort the browser */
	case 'q':
	case 'Q':
	case 'e':	/* Pico compatibility, yeech */
	case 'E':
2698
	case NANO_CANCEL_KEY:
2699
	case NANO_EXIT_FKEY:
2700
2701
	    abort = 1;
	    break;
Chris Allegretta's avatar
Chris Allegretta committed
2702
2703
2704
2705
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2706
2707
	blank_edit();

Chris Allegretta's avatar
Chris Allegretta committed
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
	if (width)
	    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 */
2723
2724
	    /* We use lstat here to detect links; then, if we find a
		symlink, we examine it via stat() to see if it is a
2725
2726
		directory or just a file symlink */
	    lstat(filelist[j], &st);
Chris Allegretta's avatar
Chris Allegretta committed
2727
2728
2729
	    if (S_ISDIR(st.st_mode))
		strcpy(foo + longest - 5, "(dir)");
	    else {
2730
2731
2732
2733
2734
2735
2736
2737
		if (S_ISLNK(st.st_mode)) {
		     /* Aha!  It's a symlink!  Now, is it a dir?  If so,
			mark it as such */
		    st = filestat(filelist[j]);
		    if (S_ISDIR(st.st_mode))
			strcpy(foo + longest - 5, "(dir)");
		    else
			strcpy(foo + longest - 2, "--");
2738
2739
		} else if (st.st_size < (1 << 10)) /* less than 1 K */
		    sprintf(foo + longest - 7, "%4d  B", 
2740
			(int) st.st_size);
2741
2742
2743
2744
2745
2746
		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);
Chris Allegretta's avatar
Chris Allegretta committed
2747
		else /* Its more than 1 k and less than a meg */
2748
2749
		    sprintf(foo + longest - 7, "%4d KB", 
			(int) st.st_size >> 10);
Chris Allegretta's avatar
Chris Allegretta committed
2750
2751
	    }

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2752
	    /* Highlight the currently selected file/dir */
2753
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2754
		wattron(edit, A_REVERSE);
2755
2756
	    waddstr(edit, foo);
	    if (j == selected)
Chris Allegretta's avatar
Chris Allegretta committed
2757
2758
		wattroff(edit, A_REVERSE);

Chris Allegretta's avatar
Chris Allegretta committed
2759
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2760
2761
2762
2763
2764
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
2765
	    if (col > COLS - longest) {
Chris Allegretta's avatar
Chris Allegretta committed
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
		editline++;
		wmove(edit, editline, 0);
		col = 0;
		if (width == 0)
		    width = filecols;
	    }
	}
 	wrefresh(edit);
    } while ((kbinput = wgetch(edit)) != NANO_EXIT_KEY);
    curs_set(1);
    blank_edit();
Chris Allegretta's avatar
Chris Allegretta committed
2777
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2778
    edit_refresh();
2779
    kb = keypad_on(edit, kb);
Chris Allegretta's avatar
Chris Allegretta committed
2780

Chris Allegretta's avatar
Chris Allegretta committed
2781
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2782
2783
2784
2785
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2786

2787
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2788
 starts do_browser from there, else from the current dir */
2789
char *do_browse_from(const char *inpath)
2790
2791
{
    struct stat st;
2792
2793
    char *tmp;
    char *bob;
2794

2795
    /* If there's no / in the string, we may as well start from . */
2796
    if (inpath == NULL || strchr(inpath, '/') == NULL) {
2797
#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2798
	char *from = getcwd(NULL, PATH_MAX + 1);
2799
#else
2800
	char *from = getcwd(NULL, 0);
2801
2802
2803
2804
2805
#endif

	bob = do_browser(from ? from : "./");
	free(from);
	return bob;
2806
    }
2807
2808

    /* If the string is a directory, pass do_browser that */
2809
    st = filestat(inpath);
2810
    if (S_ISDIR(st.st_mode))
2811
	return do_browser(inpath);
2812

2813
    tmp = mallocstrcpy(NULL, inpath);
2814
2815
2816
2817
    /* Okay, there's a dir in there, but not at the end of the string... 
       try stripping it off */
    striponedir(tmp);
    align(&tmp);
2818
2819
2820
    bob = do_browser(tmp);
    free(tmp);
    return bob;
2821
}
Chris Allegretta's avatar
Chris Allegretta committed
2822
#endif /* !DISABLE_BROWSER */