files.c 73 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
#ifdef ENABLE_NLS
Chris Allegretta's avatar
Chris Allegretta committed
42
43
44
45
46
47
#include <libintl.h>
#define _(string) gettext(string)
#else
#define _(string) (string)
#endif

48
49
50
51
52
/* statics for here */
#ifndef NANO_SMALL
static int fileformat = 0;	/* 0 = *nix, 1 = DOS, 2 = Mac */
#endif

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

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

65
66
67
68
#ifdef ENABLE_COLOR
    update_color();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
69
70
71
72
73
74
75
    wmove(edit, current_y, current_x);
}

/* What happens when there is no file to open? aiee! */
void new_file(void)
{
    fileage = nmalloc(sizeof(filestruct));
76
    fileage->data = charalloc(1);
Chris Allegretta's avatar
Chris Allegretta committed
77
    fileage->data[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
78
79
80
81
82
83
84
85
    fileage->prev = NULL;
    fileage->next = NULL;
    fileage->lineno = 1;
    filebot = fileage;
    edittop = fileage;
    editbot = fileage;
    current = fileage;
    totlines = 1;
86
    totsize = 0;
87
88
89

#ifdef ENABLE_MULTIBUFFER
    /* if there aren't any entries in open_files, create the entry for
90
91
       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
92
       given no open_files entry */
93
    if (!open_files) {
94
	add_open_file(0);
95
96
97
98
99
100
101
102
103
	/* 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);
104
105
#endif

106
107
108
109
#ifdef ENABLE_COLOR
    update_color();
#endif

Chris Allegretta's avatar
Chris Allegretta committed
110
111
}

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
112
filestruct *read_line(char *buf, filestruct *prev, int *line1ins, int len)
Chris Allegretta's avatar
Chris Allegretta committed
113
114
115
116
{
    filestruct *fileptr;

    fileptr = nmalloc(sizeof(filestruct));
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
117
118
119
120

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

121
    fileptr->data = charalloc(strlen(buf) + 2);
Chris Allegretta's avatar
Chris Allegretta committed
122
123
    strcpy(fileptr->data, buf);

124
#ifndef NANO_SMALL
125
126
127
    /* 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
128
	fileptr->data[strlen(buf) - 1] = '\0';
129
	totsize--;
130
131
132

	if (!fileformat)
	    fileformat = 1;
133
134
135
    }
#endif

Chris Allegretta's avatar
Chris Allegretta committed
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
    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
164
int read_file(FILE *f, const char *filename, int quiet)
Chris Allegretta's avatar
Chris Allegretta committed
165
{
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
166
    int num_lines = 0, len = 0;
167
    char input = 0; 		/* current input character */
Chris Allegretta's avatar
Chris Allegretta committed
168
169
170
171
    char *buf;
    long i = 0, bufx = 128;
    filestruct *fileptr = current, *tmp = NULL;
    int line1ins = 0;
172
    int input_int;
Chris Allegretta's avatar
Chris Allegretta committed
173

174
    buf = charalloc(bufx);
175
    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
176
177
178
179
180
181
182
183
184
185

    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 */
186
    while ((input_int = getc(f)) != EOF) {
Chris Allegretta's avatar
Chris Allegretta committed
187
        input = (char)input_int;
Chris Allegretta's avatar
Chris Allegretta committed
188
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
189
190
	if (!ISSET(NO_CONVERT) && is_cntrl_char((int)input)
		&& input != '\t' && input != '\r' && input != '\n') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
191
192
193
194
	    /* 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
195
196
#endif

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

201
	if (input == '\n') {
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
202
203
204
205
206
207
208
209
210
211

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

212
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
213
	    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
214
	    i = 0;
Chris Allegretta's avatar
Chris Allegretta committed
215
#ifndef NANO_SMALL
216
217
	/* 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
218
	} else if (!ISSET(NO_CONVERT) && i > 0 && buf[i - 1] == '\r') {
219
	    fileformat = 2;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
220
221
222
223
224
225
226
227
228
229

	    /* 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
230
	    num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
231
	    totsize++;
232
	    buf[0] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
233
	    buf[1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
234
235
	    i = 1;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
236
237
238
239
	} 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
240
	       decrease it at all.  We do free it at the end, though. */
Chris Allegretta's avatar
Chris Allegretta committed
241
242
243
244
245

	    if (i >= bufx - 1) {
		buf = nrealloc(buf, bufx + 128);
		bufx += 128;
	    }
246
	    buf[i] = input;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
247
	    buf[i + 1] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
248
249
	    i++;
	}
250
251
252
253
254
255
256
	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
257
258
259
    }

    /* Did we not get a newline but still have stuff to do? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
260
261
    if (len > 0) {

262
263
264
265
266
267
268
269
#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
270
271
272
	/* read in the LAST line properly */
	fileptr = read_line(buf, fileptr, &line1ins, len);

273
	num_lines++;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
274
275
	totsize++;
	buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
276
    }
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#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
293
294

    /* Did we even GET a file if we don't already have one? */
295
    if (totsize == 0 || fileptr == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
296
	new_file();
297
	statusbar(_("Read %d lines"), num_lines);
Chris Allegretta's avatar
Chris Allegretta committed
298
299
	return 1;
    }
Robert Siemborski's avatar
Robert Siemborski committed
300

301
302
303
304
305
306
307
    /* Did we try to insert a file of 0 bytes? */
    if (num_lines == 0)
    {
	statusbar(_("Read %d lines"), 0);
	return 1;
    }

Chris Allegretta's avatar
Chris Allegretta committed
308
309
310
311
312
313
314
315
    if (current != NULL) {
	fileptr->next = current;
	current->prev = fileptr;
	renumber(current);
	current_x = 0;
	placewewant = 0;
    } else if (fileptr->next == NULL) {
	filebot = fileptr;
Robert Siemborski's avatar
Robert Siemborski committed
316
	new_magicline();
Chris Allegretta's avatar
Chris Allegretta committed
317
	totsize--;
Robert Siemborski's avatar
Robert Siemborski committed
318

Chris Allegretta's avatar
Chris Allegretta committed
319
320
321
	/* Update the edit buffer; note that if using multibuffers, a
	   quiet load will update the current open_files entry, while a
	   noisy load will add a new open_files entry */
322
	load_file(quiet);
Chris Allegretta's avatar
Chris Allegretta committed
323
    }
324
325
326

#ifndef NANO_SMALL
    if (fileformat == 2)
327
	statusbar(_("Read %d lines (Converted from Mac format)"), num_lines);
328
    else if (fileformat == 1)
329
	statusbar(_("Read %d lines (Converted from DOS format)"), num_lines);
330
331
332
333
    else
#endif
	statusbar(_("Read %d lines"), num_lines);

334
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
335
336

    free(buf);
337
    fclose(f);
Chris Allegretta's avatar
Chris Allegretta committed
338
339
340
341
342

    return 1;
}

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

Chris Allegretta's avatar
Chris Allegretta committed
349
    if (filename[0] == '\0' || stat(filename, &fileinfo) == -1) {
350
351
	if (insert && !quiet) {
	    statusbar(_("\"%s\" not found"), filename);
Chris Allegretta's avatar
Chris Allegretta committed
352
353
354
355
356
357
358
359
360
	    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);
361
362
	if (!insert)
	    new_file();
Chris Allegretta's avatar
Chris Allegretta committed
363
364
	return -1;
    } else {			/* File is A-OK */
365
366
367
368
369
370
371
372
	if (S_ISDIR(fileinfo.st_mode) || S_ISCHR(fileinfo.st_mode) || 
		S_ISBLK(fileinfo.st_mode)) {
	    if (S_ISDIR(fileinfo.st_mode))
		statusbar(_("File \"%s\" is a directory"), filename);
	    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
373
374
	    if (!insert)
		new_file();
Chris Allegretta's avatar
Chris Allegretta committed
375
376
377
378
	    return -1;
	}
	if (!quiet)
	    statusbar(_("Reading File"));
379
380
381
382
383
384
	f = fdopen(fd, "rb"); /* Binary for our own line-end munging */
	if (!f) {
	    nperror("fdopen");
	    return -1;
	}
	read_file(f, filename, quiet);
385
386
387
#ifndef NANO_SMALL
	stat(filename, &originalfilestat);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
388
389
390
391
392
    }

    return 1;
}

393
394
395
/* 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
396
397
   exists, we return "". */
char *get_next_filename(const char *name)
398
399
400
401
402
403
404
405
{
    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
406
    while (1) {
407
408

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

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

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

    return buf;
}

424
int do_insertfile(int loading_file)
Chris Allegretta's avatar
Chris Allegretta committed
425
{
Chris Allegretta's avatar
Chris Allegretta committed
426
    int i, old_current_x = current_x;
427
    char *realname = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
428
429

    wrap_reset();
430

431
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
432
    currshortcut = insertfile_list;
433
434
#endif

435
#ifndef DISABLE_OPERATINGDIR
Chris Allegretta's avatar
Chris Allegretta committed
436
    if (operating_dir && (strcmp(operating_dir, "."))) {
437
438
439
440
	i = statusq(1, insertfile_list, "", _("File to insert [from %s] "),
		operating_dir);
    } else {
#endif
441
    i = statusq(1, insertfile_list, "", _("File to insert [from ./] "));
442
443
444
445
#ifndef DISABLE_OPERATINGDIR
    }
#endif

Chris Allegretta's avatar
Chris Allegretta committed
446
447
448
    if (i != -1) {

#ifdef DEBUG
449
	fprintf(stderr, _("filename is %s\n"), answer);
Chris Allegretta's avatar
Chris Allegretta committed
450
451
#endif

452
#ifndef DISABLE_TABCOMP
453
	realname = real_dir_from_tilde(answer);
454
#else
455
	realname = mallocstrcpy(realname, answer);
456
#endif
457

458
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
459
	if (i == NANO_TOFILES_KEY) {
460
461
	    
	    char *tmp = do_browse_from(realname);
462
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
463
	    currshortcut = insertfile_list;
464
#endif
Chris Allegretta's avatar
Chris Allegretta committed
465

466
#ifdef DISABLE_TABCOMP
467
	    realname = NULL;
468
469
#endif
	    if 	(tmp != NULL)
470
		realname = mallocstrcpy(realname, tmp);
471
	    else
472
473
474
475
		return do_insertfile(loading_file);
	}
#endif

476
477
478
479
480
481
482
483
484
#ifndef DISABLE_OPERATINGDIR
	if (operating_dir) {
	    if (check_operating_dir(realname, 0)) {
		statusbar(_("Can't insert file from outside of %s"), operating_dir);
		return 0;
	    }
	}
#endif

485
486
487
488
#ifndef NANO_SMALL
	if (i == NANO_EXTCMD_KEY) {
	    int ts;
	    ts = statusq(1, extcmd_list, "", _("Command to execute "));
Chris Allegretta's avatar
Chris Allegretta committed
489
	    if (ts == -1  || answer == NULL || answer[0] == '\0') {
490
491
492
493
494
495
496
497
		statusbar(_("Cancelled"));
		UNSET(KEEP_CUTBUFFER);
		display_main_list();
		return 0;
	    }
	}
#endif

498
#ifdef ENABLE_MULTIBUFFER
499
500
	if (loading_file) {

501
502
	    /* update the current entry in the open_files structure */
	    add_open_file(1);
503
504
505

	    new_file();
	    UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
506
507
508
#ifndef NANO_SMALL
	    UNSET(MARK_ISSET);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
509
510
511
	}
#endif

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

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

527
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
528

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

533
#ifdef ENABLE_MULTIBUFFER
534
	if (loading_file)
535
	    load_file(0);
536
537
538
539
	else
#endif

	    set_modified();
Chris Allegretta's avatar
Chris Allegretta committed
540
541

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

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

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

#ifdef ENABLE_MULTIBUFFER
	}
#endif

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

572
573
574
575
#ifdef ENABLE_COLOR
	update_color();
#endif    

Chris Allegretta's avatar
Chris Allegretta committed
576
577
578
579
580
581
582
583
584
585
586
	UNSET(KEEP_CUTBUFFER);
	display_main_list();
	return i;
    } else {
	statusbar(_("Cancelled"));
	UNSET(KEEP_CUTBUFFER);
	display_main_list();
	return 0;
    }
}

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

    display_main_list();
    return result;
}

607
#ifdef ENABLE_MULTIBUFFER
Chris Allegretta's avatar
Chris Allegretta committed
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
676
/* 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
    }
}

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

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

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

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

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

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

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

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

Chris Allegretta's avatar
Chris Allegretta committed
733
734
735
736
737
738
739
740
741
742
743
744
745
746
    /* 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
    }

747
748
749
750
    /* 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
751
752
753
	/* save current file buffer */
	open_files->fileage = fileage;
	open_files->filebot = filebot;
754
    }
755
756

#ifdef DEBUG
757
    fprintf(stderr, _("filename is %s\n"), open_files->filename);
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
#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 */
775
    filename = mallocstrcpy(filename, open_files->filename);
776
777
778
#ifndef NANO_SMALL
    originalfilestat = open_files->originalfilestat;
#endif
779
    fileage = open_files->fileage;
780
    current = fileage;
Chris Allegretta's avatar
Chris Allegretta committed
781
    filebot = open_files->filebot;
782
783
784
    totlines = open_files->file_totlines;
    totsize = open_files->file_totsize;

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

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

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

809
810
811
    /* 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) */
812
    if (ISSET(CONSTUPDATE))
813
	do_cursorpos(0);
814

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

819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
    /* 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
835
       doing anything */
836
    if (!closing_file)
837
	add_open_file(1);
838
839
840
841
842
843
844
845
846
847
848
849
850

    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
851
	fprintf(stderr, _("filename is %s\n"), open_files->filename);
852
853
854
855
856
857
858
859
860
861
862
#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
863
	    fprintf(stderr, _("filename is %s\n"), open_files->filename);
864
865
866
867
868
869
#endif

    }

    load_open_file();

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

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

    return 0;
}

880
881
882
883
884
885
886
/* This function is used by the shortcut list. */
int open_prevfile_void(void)
{
    open_prevfile(0);
    return 0;
}

887
888
889
890
891
892
893
894
895
896
897
898
/*
 * 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
899
       doing anything */
900
    if (!closing_file)
901
	add_open_file(1);
902
903
904
905
906
907
908
909
910
911
912
913
914

    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
915
	fprintf(stderr, _("filename is %s\n"), open_files->filename);
916
917
918
919
920
921
922
923
924
925
#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
926
	    fprintf(stderr, _("filename is %s\n"), open_files->filename);
927
928
929
930
931
932
933
#endif

	}
    }

    load_open_file();

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

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

    return 0;
}

944
945
946
947
948
949
950
/* This function is used by the shortcut list. */
int open_nextfile_void(void)
{
    open_nextfile(0);
    return 0;
}

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

    if (!open_files)
	return 1;

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

969
    tmp = open_files;
970
971
    if (open_nextfile(1)) {
	if (open_prevfile(1))
972
973
974
	    return 1;
    }

975
976
    unlink_opennode(tmp);
    delete_opennode(tmp);
977
978
979
980
981

    shortcut_init(0);
    display_main_list();
    return 0;
}
982
#endif /* MULTIBUFFER */
983

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

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

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

    if (d_here) {

	align(&d_here);
1011
1012
1013
1014
	if (strcmp(d_here, "/")) {
	    d_here = nrealloc(d_here, strlen(d_here) + 2);
	    strcat(d_here, "/");
	}
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028

	/* 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 */
	path_only = stat(origpath, &fileinfo);
	if (path_only == -1)
		path_only = 0;
	else {
	    if (S_ISDIR(fileinfo.st_mode))
		path_only = 1;
	    else
		path_only = 0;
	}
1029

1030
	/* save the value of origpath in both d_there and d_there_file */
1031
1032
1033
1034
1035
	d_there = charalloc(strlen(origpath) + 1);
	d_there_file = charalloc(strlen(origpath) + 1);
	strcpy(d_there, origpath);
	strcpy(d_there_file, origpath);

1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
	/* 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, "/");
	    }
	}

1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
	/* 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 */
	if (!last_slash) {
	    d_there = nrealloc(d_there, strlen(d_here) + 1);
	    strcpy(d_there, d_here);
	}

	else {

1061
1062
	    /* otherwise, remove all non-path elements from d_there
	       (i. e. everything after the last slash) */
1063
	    last_slash_index = strlen(d_there) - strlen(last_slash);
1064
	    null_at(&d_there, last_slash_index + 1);
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074

	    /* 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);
	    }
1075
1076
1077
1078

	    /* now go to the path specified in d_there */
	    if (chdir(d_there) != -1) {

1079
1080
1081
		/* 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 */
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091

		free(d_there);

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

		align(&d_there);
1092
		if (d_there) {
1093
1094
1095
1096
1097
1098
1099

		    /* 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, "/");
		    }
1100
1101
1102
		}
		else
		    return NULL;
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
	    }

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

1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
	/* 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);
	}
1124
1125
1126
1127
1128
1129
1130
1131
1132

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

    return newpath;
}
1133
#endif /* !DISABLE_SPELLER || !DISABLE_OPERATINGDIR */
1134
1135
1136

#ifndef DISABLE_SPELLER
/*
1137
1138
1139
 * 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.
1140
 */
1141
char *check_writable_directory(const char *path) {
1142
1143

    char *full_path = get_full_path(path);
1144
    int writable;
1145
1146
    struct stat fileinfo;

1147
1148
    /* if get_full_path() failed, return NULL */
    if (!full_path)
1149
	return NULL;
1150
1151
1152

    /* 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 */
1153
    stat(full_path, &fileinfo);
1154
1155
1156
1157
    if (fileinfo.st_mode & S_IWUSR)
	writable = 1;
    else
	writable = 0;
1158
1159

    /* if the full path doesn't end in a slash (meaning get_full_path()
1160
1161
1162
1163
       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);
1164
	return NULL;
1165
    }
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175

    /* 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
1176
1177
1178
1179
1180
1181
1182
 * 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.
1183
1184
1185
1186
 */
char *safe_tempnam(const char *dirname, const char *filename_prefix) {

    char *buf, *tempdir = NULL, *full_tempdir = NULL;
1187
    int filedesc;
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197

    /* 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 */
    if (getenv("TMPDIR") && strcmp(getenv("TMPDIR"),"")) {

	/* store the value of $TMPDIR in tempdir, run its value through
	   get_full_path(), and save the result in full_tempdir */
	tempdir = charalloc(strlen(getenv("TMPDIR")) + 1);
	sprintf(tempdir, "%s", getenv("TMPDIR"));
1198
	full_tempdir = check_writable_directory(tempdir);
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211

	/* we don't need the value of tempdir anymore */
	free(tempdir);
    }

    if (!full_tempdir) {

	/* 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 (dirname) {
	    tempdir = charalloc(strlen(dirname) + 1);
	    strcpy(tempdir, dirname);
1212
	    full_tempdir = check_writable_directory(tempdir);
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223

	    /* we don't need the value of tempdir anymore */
	    free(tempdir);
	}
    }

    /* if $TMPDIR is blank or isn't set, or if it isn't a writable
       directory, and dirname is NULL, try P_tmpdir instead */
    if (!full_tempdir) {
	tempdir = charalloc(strlen(P_tmpdir) + 1);
	strcpy(tempdir, P_tmpdir);
1224
	full_tempdir = check_writable_directory(tempdir);
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258

	/* we don't need the value of tempdir anymore */
	free(tempdir);
    }

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

    buf = charalloc(strlen(full_tempdir) + 12);
    sprintf(buf, "%s", full_tempdir);

    /* like tempnam(), use only the first 5 characters of the prefix */
    strncat(buf, filename_prefix, 5);

    strcat(buf, "XXXXXX");
    filedesc = mkstemp(buf);

    /* if mkstemp() failed, get out */
    if (filedesc == -1)
	return NULL;

    /* otherwise, close the resulting file; afterwards, it'll be 0 bytes
       long, so delete it; finally, return the filename (all that's left
       of it) */
    else {
	close(filedesc);
	unlink(buf);
	return buf;
    }
}
#endif /* !DISABLE_SPELLER */
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268

#ifndef DISABLE_OPERATINGDIR
/*
 * 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.
 */
int check_operating_dir(char *currpath, int allow_tabcomp)
{
1269
1270
1271
    /* 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
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
       only be handled properly if this is done */

    char *fullpath, *whereami1, *whereami2 = NULL;

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

    /* if the operating directory is "/", that's the same as having no
       operating directory, so discard it and get out */
    if (!strcmp(operating_dir, "/")) {
	operating_dir = NULL;
	return 0;
    }

    /* get the full operating (if we don't have it already) and current
       directories, and then search the current for the operating (for
       normal usage) and the operating for the current (for tab
       completion, if we're allowing it); if the current directory's path
       doesn't exist, assume we're outside the operating directory */
    if (!full_operating_dir) {
	full_operating_dir = get_full_path(operating_dir);

	/* if get_full_path() failed, discard the operating directory */
	if (!full_operating_dir) {
	    operating_dir = NULL;
	    return 0;
	}
1300
1301
1302
1303
1304
1305
1306
1307

	/* if the full operating directory is "/", that's the same as
	   having no operating directory, so discard it and get out */
	if (!strcmp(full_operating_dir, "/")) {
	    free(full_operating_dir);
	    operating_dir = NULL;
	    return 0;
	}
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
    }

    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 */
    if (!whereami1 && !whereami2)
	return 1;

    /* 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)
	return 1;

    /* otherwise, we're still inside it */
    return 0;
}
1332
1333
#endif

Chris Allegretta's avatar
Chris Allegretta committed
1334
1335
/*
 * Write a file out.  If tmp is nonzero, we set the umask to 0600,
1336
 * we don't set the global variable filename to its name, and don't
Chris Allegretta's avatar
Chris Allegretta committed
1337
1338
 * print out how many lines we wrote on the statusbar.
 * 
1339
 * tmp means we are writing a tmp file in a secure fashion.  We use
1340
 * it when spell checking or dumping the file on an error.
1341
 *
1342
1343
 * append == 1 means we are appending instead of overwriting.
 * append == 2 means we are prepending instead of overwriting.
1344
1345
 *
 * nonamechange means don't change the current filename, it is ignored
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1346
 * if tmp == 1 or if we're appending/prepending.
Chris Allegretta's avatar
Chris Allegretta committed
1347
 */
1348
int write_file(char *name, int tmp, int append, int nonamechange)
Chris Allegretta's avatar
Chris Allegretta committed
1349
1350
{
    long size, lineswritten = 0;
1351
    char *buf = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
1352
    filestruct *fileptr;
1353
1354
1355
    FILE *f;
    int fd;
    int mask = 0, realexists, anyexists;
1356
    struct stat st, lst;
1357
    char *realname = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
1358

Chris Allegretta's avatar
Chris Allegretta committed
1359
    if (name[0] == '\0') {
Chris Allegretta's avatar
Chris Allegretta committed
1360
1361
1362
	statusbar(_("Cancelled"));
	return -1;
    }
Chris Allegretta's avatar
Chris Allegretta committed
1363
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1364
    fileptr = fileage;
1365
1366
1367
1368

    if (realname != NULL)
	free(realname);

Chris Allegretta's avatar
Chris Allegretta committed
1369
1370
1371
    if (buf != NULL)
	free(buf);

1372
#ifndef DISABLE_TABCOMP
1373
1374
1375
1376
    realname = real_dir_from_tilde(name);
#else
    realname = mallocstrcpy(realname, name);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1377

1378
1379
#ifndef DISABLE_OPERATINGDIR
    if (!tmp && operating_dir) {
Chris Allegretta's avatar
Chris Allegretta committed
1380
1381
1382
	/* if we're writing a temporary file, we're probably going
	   outside the operating directory, so skip the operating
	   directory test */
1383
1384
1385
1386
1387
1388
1389
1390
	if (check_operating_dir(realname, 0)) {
	    statusbar(_("Can't write outside of %s"), operating_dir);

	    return -1;
	}
    }
#endif

1391
    /* Save the state of file at the end of the symlink (if there is one) */
1392
1393
    realexists = stat(realname, &st);

1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
#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
       appending, prepending, or writing a selection, also backup only if
       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)
	    statusbar(_("Could not set permissions %o on backup %s: %s"),   
			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

1463
1464
    /* Stat the link itself for the check... */
    anyexists = lstat(realname, &lst);
1465

1466
1467
    /* New case: if the file exists, just give up */
    if (tmp && anyexists != -1)
1468
	return -1;
1469
    /* NOTE: If you change this statement, you MUST CHANGE the if 
1470
1471
1472
       statement below (that says:
		if (realexists == -1 || tmp || (!ISSET(FOLLOW_SYMLINKS) &&
		S_ISLNK(lst.st_mode))) {
1473
       to reflect whether or not to link/unlink/rename the file */
1474
1475
    else if (append != 2 && (ISSET(FOLLOW_SYMLINKS) || !S_ISLNK(lst.st_mode) 
		|| tmp)) {
Chris Allegretta's avatar
Chris Allegretta committed
1476
	/* Use O_EXCL if tmp == 1.  This is now copied from joe, because
1477
	   wiggy says so *shrug*. */
1478
	if (append)
1479
	    fd = open(realname, O_WRONLY | O_CREAT | O_APPEND, (S_IRUSR|S_IWUSR));
1480
	else if (tmp)
1481
1482
1483
	    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));
1484
1485

	/* First, just give up if we couldn't even open the file */
1486
	if (fd == -1) {
1487
	    if (!tmp && ISSET(TEMP_OPT)) {
Chris Allegretta's avatar
Chris Allegretta committed
1488
		UNSET(TEMP_OPT);
1489
		return do_writeout(filename, 1, 0);
Chris Allegretta's avatar
Chris Allegretta committed
1490
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1491
1492
1493
1494
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
	    return -1;
	}
1495

Chris Allegretta's avatar
Chris Allegretta committed
1496
1497
1498
    }
    /* Don't follow symlink.  Create new file. */
    else {
1499
	buf = charalloc(strlen(realname) + 8);
1500
	strcpy(buf, realname);
Chris Allegretta's avatar
Chris Allegretta committed
1501
1502
	strcat(buf, ".XXXXXX");
	if ((fd = mkstemp(buf)) == -1) {
Chris Allegretta's avatar
Chris Allegretta committed
1503
1504
	    if (ISSET(TEMP_OPT)) {
		UNSET(TEMP_OPT);
1505
		return do_writeout(filename, 1, 0);
1506
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1507
1508
1509
1510
1511
1512
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
	    return -1;
	}
    }

Chris Allegretta's avatar
Chris Allegretta committed
1513
#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
1514
    dump_buffer(fileage);
Chris Allegretta's avatar
Chris Allegretta committed
1515
#endif
1516

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1517
    f = fdopen(fd, append == 1 ? "ab" : "wb");
1518
1519
1520
1521
1522
1523
    if (!f) {
        statusbar(_("Could not open file for writing: %s"),
                  strerror(errno));
        return -1;
    }

Chris Allegretta's avatar
Chris Allegretta committed
1524
    while (fileptr != NULL && fileptr->next != NULL) {
1525
	int data_len;
Robert Siemborski's avatar
Robert Siemborski committed
1526
	/* Next line is so we discount the "magic line" */
1527
1528
	if (filebot == fileptr && fileptr->data[0] == '\0')
	    break;
Robert Siemborski's avatar
Robert Siemborski committed
1529

1530
	data_len = strlen(fileptr->data);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1531
1532
1533
1534

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

1535
	size = fwrite(fileptr->data, 1, data_len, f);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1536
1537
1538
1539

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

1540
	if (size < data_len) {
Chris Allegretta's avatar
Chris Allegretta committed
1541
1542
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
1543
	    fclose(f);
Chris Allegretta's avatar
Chris Allegretta committed
1544
	    return -1;
1545
	}
Chris Allegretta's avatar
Chris Allegretta committed
1546
#ifdef DEBUG
1547
	else
Chris Allegretta's avatar
Chris Allegretta committed
1548
1549
	    fprintf(stderr, _("Wrote >%s\n"), fileptr->data);
#endif
1550
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1551
	if (ISSET(DOS_FILE) || ISSET(MAC_FILE))
1552
	    putc('\r', f);
Chris Allegretta's avatar
Chris Allegretta committed
1553
1554

	if (!ISSET(MAC_FILE))
1555
#endif
1556
	    putc('\n', f);
Chris Allegretta's avatar
Chris Allegretta committed
1557
1558
1559
1560
1561
1562

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

    if (fileptr != NULL) {
1563
	int data_len;
1564

1565
	data_len = strlen(fileptr->data);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1566
1567
1568
1569

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

1570
	size = fwrite(fileptr->data, 1, data_len, f);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1571
1572
1573
1574

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

1575
	if (size < data_len) {
Chris Allegretta's avatar
Chris Allegretta committed
1576
1577
1578
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
	    return -1;
1579
	} else if (data_len > 0) {
1580
#ifndef NANO_SMALL
Chris Allegretta's avatar
Chris Allegretta committed
1581
	    if (ISSET(DOS_FILE) || ISSET(MAC_FILE)) {
1582
		if (putc('\r', f) == EOF) {
1583
1584
		    statusbar(_("Could not open file for writing: %s"),
			  strerror(errno));
1585
		    fclose(f);
1586
1587
		    return -1;
		}
1588
		lineswritten++;
1589
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1590
1591

	    if (!ISSET(MAC_FILE))
1592
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1593
	    {
1594
		if (putc('\n', f) == EOF) {
Chris Allegretta's avatar
Chris Allegretta committed
1595
		    statusbar(_("Could not open file for writing: %s"),
Chris Allegretta's avatar
Chris Allegretta committed
1596
			  strerror(errno));
1597
		    fclose(f);
Chris Allegretta's avatar
Chris Allegretta committed
1598
1599
		    return -1;
		}
1600
		lineswritten++;
Chris Allegretta's avatar
Chris Allegretta committed
1601
1602
1603
1604
	    }
	}
    }

1605
    if (fclose(f)) {
1606
	statusbar(_("Could not close %s: %s"), realname, strerror(errno));
Chris Allegretta's avatar
Chris Allegretta committed
1607
1608
1609
1610
	unlink(buf);
	return -1;
    }

1611
1612
    /* if we're prepending, open the real file, and append it here */
    if (append == 2) {
1613
1614
1615
	int fd_source, fd_dest;
	FILE *f_source, *f_dest;
	int prechar;
1616
1617

	if ((fd_dest = open(buf, O_WRONLY | O_APPEND, (S_IRUSR|S_IWUSR))) == -1) {
1618
1619
1620
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
	    return -1;
	}
1621
1622
1623
	f_dest = fdopen(fd_dest, "wb");
	if (!f_dest) {
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
1624
	    close(fd_dest);
1625
1626
	    return -1;
	}
1627
	if ((fd_source = open(realname, O_RDONLY | O_CREAT)) == -1) {
1628
	    statusbar(_("Could not open %s for prepend: %s"), realname, strerror(errno));
1629
	    fclose(f_dest);
1630
1631
1632
1633
1634
1635
1636
	    return -1;
	}
	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);
1637
1638
1639
	    return -1;
	}

1640
1641
1642
1643
1644
1645
1646
1647
1648
        /* 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);
		return -1;
	    }
	}
1649

1650
1651
1652
1653
1654
1655
1656
1657
1658
	if (ferror(f_source)) {
	    statusbar(_("Could not reopen %s: %s"), buf, strerror(errno));
	    fclose(f_source);
	    fclose(f_dest);
	    return -1;
	}
	    
	fclose(f_source);
	fclose(f_dest);
1659
1660
    }

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

1664
	/* Use default umask as file permissions if file is a new file. */
1665
1666
	mask = umask(0);
	umask(mask);
Chris Allegretta's avatar
Chris Allegretta committed
1667

1668
	if (tmp)	/* We don't want anyone reading our temporary file! */
1669
1670
1671
1672
	    mask = 0600;
	else
	    mask = 0666 & ~mask;
    } else
Chris Allegretta's avatar
Chris Allegretta committed
1673
	/* Use permissions from file we are overwriting. */
1674
1675
	mask = st.st_mode;

1676
1677
    if (append == 2 || 
		(!tmp && (!ISSET(FOLLOW_SYMLINKS) && S_ISLNK(lst.st_mode)))) {
1678
1679
	if (unlink(realname) == -1) {
	    if (errno != ENOENT) {
1680
		statusbar(_("Could not open %s for writing: %s"),
1681
			  realname, strerror(errno));
1682
1683
1684
		unlink(buf);
		return -1;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1685
	}
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
	if (link(buf, realname) != -1)
	    unlink(buf);
	else if (errno != EPERM) {
	    statusbar(_("Could not open %s for writing: %s"),
		      name, strerror(errno));
	    unlink(buf);
	    return -1;
	} else if (rename(buf, realname) == -1) {	/* Try a rename?? */
	    statusbar(_("Could not open %s for writing: %s"),
		      realname, strerror(errno));
	    unlink(buf);
	    return -1;
	}
Chris Allegretta's avatar
Chris Allegretta committed
1699
    }
1700
1701
1702
1703
    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
1704
    if (!tmp && !append) {
Chris Allegretta's avatar
Chris Allegretta committed
1705
	if (!nonamechange)
1706
1707
	    filename = mallocstrcpy(filename, realname);

1708
1709
1710
1711
#ifndef NANO_SMALL
	/* Update originalfilestat to reference the file as it is now. */
	stat(filename, &originalfilestat);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1712
	statusbar(_("Wrote %d lines"), lineswritten);
1713
	UNSET(MODIFIED);
Chris Allegretta's avatar
Chris Allegretta committed
1714
	titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
1715
1716
1717
1718
    }
    return 1;
}

1719
int do_writeout(char *path, int exiting, int append)
Chris Allegretta's avatar
Chris Allegretta committed
1720
1721
{
    int i = 0;
1722
1723
1724
#ifndef NANO_SMALL
    const char *formatstr, *backupstr;
#endif
1725
1726
1727
#ifdef NANO_EXTRA
    static int did_cred = 0;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1728

1729
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1730
    currshortcut = writefile_list;
1731
1732
#endif

1733
    answer = mallocstrcpy(answer, path);
Chris Allegretta's avatar
Chris Allegretta committed
1734

1735
    if ((exiting) && (ISSET(TEMP_OPT))) {
1736
	if (filename[0]) {
1737
	    i = write_file(answer, 0, 0, 0);
1738
1739
	    display_main_list();
	    return i;
1740
	} else {
Chris Allegretta's avatar
Chris Allegretta committed
1741
1742
1743
1744
1745
	    UNSET(TEMP_OPT);
	    do_exit();

	    /* They cancelled, abort quit */
	    return -1;
1746
	}
Chris Allegretta's avatar
Chris Allegretta committed
1747
1748
1749
    }

    while (1) {
1750

1751
#ifndef NANO_SMALL
1752
1753
1754
1755
1756
1757
1758
	if (ISSET(MAC_FILE))
	   formatstr = _(" [Mac Format]");
	else if (ISSET(DOS_FILE))
	   formatstr = _(" [DOS Format]");
	else
	   formatstr = "";

1759
1760
1761
1762
1763
	if (ISSET(BACKUP_FILE))
	   backupstr = _(" [Backup]");
	else
	   backupstr = "";

1764
	/* Be nice to the translation folks */
1765
	if (ISSET(MARK_ISSET) && !exiting) {
1766
1767
	    if (append == 2)
		i = statusq(1, writefile_list, "",
1768
		    "%s%s%s", _("Prepend Selection to File"), formatstr, backupstr);
1769
	    else if (append)
1770
		i = statusq(1, writefile_list, "",
1771
		    "%s%s%s", _("Append Selection to File"), formatstr, backupstr);
1772
	    else
1773
		i = statusq(1, writefile_list, "",
1774
1775
		    "%s%s%s", _("Write Selection to File"), formatstr, backupstr);
	} else {
1776
1777
	    if (append == 2)
		i = statusq(1, writefile_list, answer,
1778
		    "%s%s%s", _("File Name to Prepend to"), formatstr, backupstr);
1779
	    else if (append)
1780
		i = statusq(1, writefile_list, answer,
1781
		    "%s%s%s", _("File Name to Append to"), formatstr, backupstr);
1782
	    else
1783
		i = statusq(1, writefile_list, answer,
1784
		    "%s%s%s", _("File Name to Write"), formatstr, backupstr);
1785
	}
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
#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
1797

Chris Allegretta's avatar
Chris Allegretta committed
1798
	if (i != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
1799

1800
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
1801
	if (i == NANO_TOFILES_KEY) {
1802
1803

	    char *tmp = do_browse_from(answer);
1804

1805
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE)
1806
	    currshortcut = writefile_list;
1807
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1808

1809
	    if (tmp != NULL) {
1810
		answer = mallocstrcpy(answer, tmp);
1811
1812
1813
	    } else
		return do_writeout(answer, exiting, append);
	} else
Chris Allegretta's avatar
Chris Allegretta committed
1814
#endif
Chris Allegretta's avatar
Chris Allegretta committed
1815
#ifndef NANO_SMALL
1816
1817
1818
1819
1820
1821
1822
1823
	if (i == TOGGLE_DOS_KEY) {
	    UNSET(MAC_FILE);
	    TOGGLE(DOS_FILE);
	    return(do_writeout(answer, exiting, append));
	} else if (i == TOGGLE_MAC_KEY) {
	    UNSET(DOS_FILE);
	    TOGGLE(MAC_FILE);
	    return(do_writeout(answer, exiting, append));
1824
1825
1826
	} else if (i == TOGGLE_BACKUP_KEY) {
	    TOGGLE(BACKUP_FILE);
	    return(do_writeout(answer, exiting, append));
Chris Allegretta's avatar
Chris Allegretta committed
1827
1828
#else
	if (0) {
1829
#endif
1830
	} else if (i == NANO_PREPEND_KEY)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1831
	    return(do_writeout(answer, exiting, append == 2 ? 0 : 2));
1832
	else if (i == NANO_APPEND_KEY)
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1833
	    return(do_writeout(answer, exiting, append == 1 ? 0 : 1));
Chris Allegretta's avatar
Chris Allegretta committed
1834

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

#ifdef NANO_EXTRA
1840
1841
	    if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy")
		&& !did_cred) {
1842
1843
		do_credits();
		did_cred = 1;
1844
		return -1;
1845
1846
	    }
#endif
1847
	    if (!append && strcmp(answer, filename)) {
Chris Allegretta's avatar
Chris Allegretta committed
1848
1849
1850
1851
1852
1853
		struct stat st;
		if (!stat(answer, &st)) {
		    i = do_yesno(0, 0, _("File exists, OVERWRITE ?"));

		    if (!i || (i == -1))
			continue;
Chris Allegretta's avatar
Chris Allegretta committed
1854
1855
		}
	    }
Chris Allegretta's avatar
Chris Allegretta committed
1856

1857
#ifndef NANO_SMALL
1858
1859
1860
1861
1862
1863
	/* Here's where we allow the selected text to be written to 
	   a separate file. */
	if (ISSET(MARK_ISSET) && !exiting) {
	    filestruct *fileagebak = fileage;	
	    filestruct *filebotbak = filebot;
	    filestruct *cutback = cutbuffer;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1864
	    long totsizebak = totsize;
1865
	    int oldmod = 0;
1866
	    cutbuffer = NULL;
1867
1868
1869
1870
1871
1872
1873

	    /* Okay, since write_file changes the filename, back it up */
	    if (ISSET(MODIFIED))
		oldmod = 1;

	    /* Now, non-destructively add the marked text to the
	       cutbuffer, and write the file out using the cutbuffer ;) */
1874
	    if (current->lineno <= mark_beginbuf->lineno)
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
		cut_marked_segment(current, current_x, mark_beginbuf,
				mark_beginx, 0);
	    else
		cut_marked_segment(mark_beginbuf, mark_beginx, current,
				current_x, 0);

	    fileage = cutbuffer;
	    for (filebot = cutbuffer; filebot->next != NULL; 
			filebot = filebot->next)
		;
1885
	    i = write_file(answer, 0, append, 1);
1886
1887
1888
1889
1890

	    /* Now restore everything */
	    fileage = fileagebak;
	    filebot = filebotbak;
	    cutbuffer = cutback;
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1891
	    totsize = totsizebak;
1892
1893
1894
	    if (oldmod)
		set_modified();
	} else
1895
1896
#endif /* !NANO_SMALL */

1897
	    i = write_file(answer, 0, append, 0);
1898

1899
#ifdef ENABLE_MULTIBUFFER
1900
1901
	    /* if we're not about to exit, update the current entry in
	       the open_files structure */
Chris Allegretta's avatar
Chris Allegretta committed
1902
1903
	    if (!exiting)
		add_open_file(1);
1904
1905
#endif

Chris Allegretta's avatar
Chris Allegretta committed
1906
1907
	    display_main_list();
	    return i;
Chris Allegretta's avatar
Chris Allegretta committed
1908
	} else {
Chris Allegretta's avatar
Chris Allegretta committed
1909
1910
1911
	    statusbar(_("Cancelled"));
	    display_main_list();
	    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
1912
	}
Chris Allegretta's avatar
Chris Allegretta committed
1913
1914
1915
1916
1917
    }
}

int do_writeout_void(void)
{
1918
    return do_writeout(filename, 0, 0);
Chris Allegretta's avatar
Chris Allegretta committed
1919
}
Chris Allegretta's avatar
Chris Allegretta committed
1920

1921
#ifndef DISABLE_TABCOMP
1922
1923
1924
1925

/* Return a malloc()ed string containing the actual directory, used
 * to convert ~user and ~/ notation...
 */
1926
char *real_dir_from_tilde(char *buf)
1927
{
1928
1929
    char *dirtmp = NULL, *find_user = NULL;
    int i = 1;
1930
    struct passwd *userdata;
1931

1932
1933
1934
    /* set a default value for dirtmp, in the case user home dir not found */
    dirtmp = mallocstrcpy(dirtmp, buf);

1935
    if (buf[0] == '~') {
1936
	if (buf[1] == 0 || buf[1] == '/') {
1937
	    if (getenv("HOME") != NULL) {
1938
1939

		free(dirtmp);
1940
		dirtmp = charalloc(strlen(buf) + 2 + strlen(getenv("HOME")));
1941

1942
		sprintf(dirtmp, "%s%s", getenv("HOME"), &buf[1]);
1943

1944
	    }
1945
1946
	}
	else {
1947

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

	    find_user = mallocstrcpy(find_user, &buf[1]);
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1953
	    find_user[i - 1] = '\0';
1954

1955
	    for (userdata = getpwent(); userdata != NULL && 
1956
		  strcmp(userdata->pw_name, find_user); 
1957
		  userdata = getpwent());
1958

1959
1960
1961
	    free(find_user);

	    if (userdata != NULL) {  /* User found */
1962

1963
	        free(dirtmp);
1964
		dirtmp = charalloc(strlen(buf) + 2 + strlen(userdata->pw_dir));
1965
1966
1967
		sprintf(dirtmp, "%s%s", userdata->pw_dir, &buf[i]);

	    }
1968

1969
	    endpwent();
1970
	}
1971
    }
1972
1973

    return dirtmp;
1974
1975
1976
}

/* Tack a slash onto the string we're completing if it's a directory */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1977
int append_slash_if_dir(char *buf, int *lastwastab, int *place)
1978
1979
1980
{
    char *dirptr;
    struct stat fileinfo;
1981
    int ret = 0;
1982
1983
1984
1985

    dirptr = real_dir_from_tilde(buf);

    if (stat(dirptr, &fileinfo) == -1)
1986
	ret = 0;
1987
1988
1989
1990
    else if (S_ISDIR(fileinfo.st_mode)) {
	strncat(buf, "/", 1);
	*place += 1;
	/* now we start over again with # of tabs so far */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
1991
	*lastwastab = 0;
1992
	ret = 1;
1993
1994
1995
    }

    if (dirptr != buf)
1996
	free(dirptr);
1997
1998

    return ret;
1999
}
Chris Allegretta's avatar
Chris Allegretta committed
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021

/*
 * 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)
{
2022
    char **matches = (char **) NULL;
2023
    char *matchline = NULL;
2024
    struct passwd *userdata;
2025

2026
    *num_matches = 0;
2027
    matches = nmalloc(BUFSIZ * sizeof(char *));
2028

2029
    strcat(buf, "*");
2030

2031
    while ((userdata = getpwent()) != NULL) {
2032

2033
	if (check_wildcard_match(userdata->pw_name, &buf[1]) == TRUE) {
2034
2035
2036
2037

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

2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
#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

2049
	    matchline = charalloc(strlen(userdata->pw_name) + 2);
2050
2051
2052
	    sprintf(matchline, "~%s", userdata->pw_name);
	    matches[*num_matches] = matchline;
	    ++*num_matches;
2053

2054
2055
2056
	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
2057
	}
2058
2059
    }
    endpwent();
2060

Chris Allegretta's avatar
Chris Allegretta committed
2061
2062
2063
2064
2065
2066
2067
2068
    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
2069
    char *dirname, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
2070
2071
2072
2073
    char **matches = (char **) NULL;
    DIR *dir;
    struct dirent *next;

2074
    matches = nmalloc(BUFSIZ * sizeof(char *));
Chris Allegretta's avatar
Chris Allegretta committed
2075
2076
2077
2078

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

2079
    /* Okie, if there's a / in the buffer, strip out the directory part */
Chris Allegretta's avatar
Chris Allegretta committed
2080
    if (buf[0] != '\0' && strstr(buf, "/")) {
Chris Allegretta's avatar
Chris Allegretta committed
2081
	dirname = charalloc(strlen(buf) + 1);
Chris Allegretta's avatar
Chris Allegretta committed
2082
2083
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
2084
	    tmp--;
2085

Chris Allegretta's avatar
Chris Allegretta committed
2086
2087
	tmp++;

Chris Allegretta's avatar
Chris Allegretta committed
2088
2089
	strncpy(dirname, buf, tmp - buf + 1);
	dirname[tmp - buf] = '\0';
2090

Chris Allegretta's avatar
Chris Allegretta committed
2091
    } else {
2092
2093

#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2094
	if ((dirname = getcwd(NULL, PATH_MAX + 1)) == NULL)
2095
#else
2096
	/* The better, but apparently segfault-causing way */
Chris Allegretta's avatar
Chris Allegretta committed
2097
	if ((dirname = getcwd(NULL, 0)) == NULL)
2098
#endif /* PATH_MAX */
Chris Allegretta's avatar
Chris Allegretta committed
2099
2100
2101
2102
2103
2104
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2105
    fprintf(stderr, "\nDir = %s\n", dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2106
2107
2108
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
2109

Chris Allegretta's avatar
Chris Allegretta committed
2110
2111
2112
    dirtmp = real_dir_from_tilde(dirname);
    free(dirname);
    dirname = dirtmp;
2113
2114

#ifdef DEBUG
Chris Allegretta's avatar
Chris Allegretta committed
2115
    fprintf(stderr, "\nDir = %s\n", dirname);
2116
2117
2118
2119
2120
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
2121
    dir = opendir(dirname);
Chris Allegretta's avatar
Chris Allegretta committed
2122
2123
2124
2125
2126
2127
2128
2129
2130
    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
2131
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
2132
2133
2134
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
2135
2136
2137
2138

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
2139
2140
2141
2142
2143
2144
2145
2146
2147

#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
2148
2149
		tmp2 = charalloc(strlen(dirname) + strlen(next->d_name) + 2);
		strcpy(tmp2, dirname);
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
		strcat(tmp2, "/");
		strcat(tmp2, next->d_name);
		if (check_operating_dir(tmp2, 1)) {
		    free(tmp2);
		    continue;
		}
	        free(tmp2);
	    }
#endif

2160
	    tmp2 = NULL;
2161
	    tmp2 = charalloc(strlen(next->d_name) + 1);
2162
2163
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
2164
	    ++*num_matches;
2165
2166
2167
2168

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
2169
2170
2171
2172
2173
2174
	}
    }

    return (matches);
}

2175
/* This function now has an arg which refers to how much the 
Chris Allegretta's avatar
Chris Allegretta committed
2176
2177
 * statusbar (place) should be advanced, i.e. the new cursor pos.
 */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2178
char *input_tab(char *buf, int place, int *lastwastab, int *newplace, int *list)
Chris Allegretta's avatar
Chris Allegretta committed
2179
2180
{
    /* Do TAB completion */
2181
    static int num_matches = 0, match_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2182
    static char **matches = (char **) NULL;
2183
    int pos = place, i = 0, col = 0, editline = 0;
2184
    int longestname = 0, is_dir = 0;
2185
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
2186

2187
2188
    *list = 0;

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2189
    if (*lastwastab == FALSE) {
2190
	char *tmp, *copyto, *matchBuf;
Chris Allegretta's avatar
Chris Allegretta committed
2191

David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2192
	*lastwastab = 1;
2193

Chris Allegretta's avatar
Chris Allegretta committed
2194
2195
	/* Make a local copy of the string -- up to the position of the
	   cursor */
2196
	matchBuf = (char *) nmalloc((strlen(buf) + 2) * sizeof(char));
2197
	memset(matchBuf, '\0', (strlen(buf) + 2));
2198

Chris Allegretta's avatar
Chris Allegretta committed
2199
2200
2201
2202
	strncpy(matchBuf, buf, place);
	tmp = matchBuf;

	/* skip any leading white space */
2203
	while (*tmp && isspace((int) *tmp))
Chris Allegretta's avatar
Chris Allegretta committed
2204
2205
2206
2207
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
2208
2209
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
2210
2211
	    free(matches);
	    matches = (char **) NULL;
2212
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2213
2214
2215
2216
2217
	}

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

2218
	/* FIXME -- this check is broken! */
2219
2220
	if (*tmp == '~' && !strchr(tmp, '/'))
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
2221
2222
2223
2224
2225
2226
2227
2228
2229

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

2230
2231
2232
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2233
	/* Did we find exactly one match? */
2234
	switch (num_matches) {
2235
	case 0:
2236
	    blank_edit();
2237
	    wrefresh(edit);
2238
2239
	    break;
	case 1:
2240
2241
2242

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

Chris Allegretta's avatar
Chris Allegretta committed
2243
	    if (buf[0] != '\0' && strstr(buf, "/")) {
2244
2245
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2246
		tmp++;
2247
	    } else
2248
2249
		tmp = buf;

2250
	    if (!strcmp(tmp, matches[0]))
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2251
		is_dir = append_slash_if_dir(buf, lastwastab, newplace);
2252
2253
2254

	    if (is_dir)
		break;
2255
2256

	    copyto = tmp;
2257
2258
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
2259
2260
		tmp++;

2261
	    /* write out the matched name */
2262
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
2263
2264
	    *newplace += strlen(matches[0]) - pos;

2265
2266
2267
2268
2269
2270
2271
	    /* 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;

2272
	    /* Is it a directory? */
David Lawrence Ramsey's avatar
David Lawrence Ramsey committed
2273
	    append_slash_if_dir(buf, lastwastab, newplace);
2274

2275
2276
	    break;
	default:
2277
	    /* Check to see if all matches share a beginning, and, if so,
2278
	       tack it onto buf and then beep */
2279

Chris Allegretta's avatar
Chris Allegretta committed
2280
	    if (buf[0] != '\0' && strstr(buf, "/")) {
2281
2282
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
2283
		tmp++;
2284
	    } else
2285
2286
2287
		tmp = buf;

	    for (pos = 0; *tmp == matches[0][pos] && *tmp != 0 &&
2288
		 pos <= strlen(matches[0]); pos++)
2289
2290
		tmp++;

2291
2292
2293
2294
2295
2296
2297
2298
2299
	    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++;
		}
2300
2301
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
2302
		    /* All the matches have the same character at pos+1,
2303
		       so paste it into buf... */
2304
		    buf = nrealloc(buf, strlen(buf) + 2);
2305
		    strncat(buf, matches[0] + pos, 1);
2306
		    *newplace += 1;
2307
		    pos++;
2308
		} else {
2309
2310
2311
2312
		    beep();
		    break;
		}
	    }
2313
	    break;
2314
	}
Chris Allegretta's avatar
Chris Allegretta committed
2315
2316
2317
2318
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
2319
	if (matches && num_matches > 1) {
Chris Allegretta's avatar
Chris Allegretta committed
2320
2321
2322
2323
2324

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

2325
	    editline = 0;
2326

2327
2328
2329
2330
2331
2332
2333
2334
	    /* 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;

2335
	    foo = charalloc(longestname + 5);
2336

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

2340
		/* make each filename shown be the same length as the longest
2341
		   filename, with two spaces at the end */
2342
2343
2344
2345
2346
2347
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

2348
2349
		/* Disable el cursor */
		curs_set(0);
2350
2351
2352
2353
2354
2355
		/* 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 */
2356
		if (col > (COLS - longestname) && matches[i + 1] != NULL) {
2357
2358
		    editline++;
		    wmove(edit, editline, 0);
2359
2360
2361
2362
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
2363
2364
2365
		    col = 0;
		}
	    }
2366
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2367
	    wrefresh(edit);
2368
	    *list = 1;
2369
2370
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
2371
2372
    }

2373
2374
2375
2376
    /* Only refresh the edit window if we don't have a list of filename
       matches on it */
    if (*list == 0)
	edit_refresh();
2377
2378
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
2379
}
2380
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2381

2382
#ifndef DISABLE_BROWSER
Chris Allegretta's avatar
Chris Allegretta committed
2383
2384

/* Return the stat of the file pointed to by path */
Chris Allegretta's avatar
Chris Allegretta committed
2385
2386
struct stat filestat(const char *path)
{
Chris Allegretta's avatar
Chris Allegretta committed
2387
2388
    struct stat st;

2389
    stat(path, &st);
Chris Allegretta's avatar
Chris Allegretta committed
2390
2391
2392
2393
2394
2395
    return st;
}

/* Our sort routine for file listings - sort directories before
 * files, and then alphabetically
 */ 
Chris Allegretta's avatar
Chris Allegretta committed
2396
2397
int diralphasort(const void *va, const void *vb)
{
Chris Allegretta's avatar
Chris Allegretta committed
2398
2399
    struct stat file1info, file2info;
    char *a = *(char **)va, *b = *(char **)vb;
2400
    int aisdir, bisdir;
Chris Allegretta's avatar
Chris Allegretta committed
2401

2402
2403
    aisdir = (stat(a, &file1info) != -1) && S_ISDIR(file1info.st_mode);
    bisdir = (stat(b, &file2info) != -1) && S_ISDIR(file2info.st_mode);
Chris Allegretta's avatar
Chris Allegretta committed
2404

2405
2406
2407
2408
2409
2410
2411
2412
    if (aisdir && !bisdir) return -1;
    if (!aisdir && bisdir) return 1;

#ifdef HAVE_STRCASECMP
    return(strcasecmp(a,b));
#else
    return(strcmp(a,b));
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2413
2414
2415

}

Chris Allegretta's avatar
Chris Allegretta committed
2416
/* Initialize the browser code, including the list of files in *path */
Chris Allegretta's avatar
Chris Allegretta committed
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
char **browser_init(char *path, int *longest, int *numents)
{
    DIR *dir;
    struct dirent *next;
    char **filelist = (char **) NULL;
    int i = 0;

    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 *));

    while ((next = readdir(dir)) != NULL) {
	if (!strcmp(next->d_name, "."))
	   continue;
2444
	filelist[i] = charalloc(strlen(next->d_name) + strlen(path) + 2);
Chris Allegretta's avatar
Chris Allegretta committed
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460

	if (!strcmp(path, "/"))
	    snprintf(filelist[i], strlen(next->d_name) + strlen(path) + 1, 
			"%s%s", path, next->d_name);
	else
	    snprintf(filelist[i], strlen(next->d_name) + strlen(path) + 2, 
			"%s/%s", path, next->d_name);
	i++;
    }

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

    return filelist;
}

2461
/* Free our malloc()ed memory */
Chris Allegretta's avatar
Chris Allegretta committed
2462
2463
2464
2465
void free_charptrarray(char **array, int len)
{
    int i;

2466
    for (i = 0; i < len; i++)
Chris Allegretta's avatar
Chris Allegretta committed
2467
2468
2469
2470
	free(array[i]);
    free(array);
}

2471
/* only print the last part of a path; isn't there a shell 
Chris Allegretta's avatar
Chris Allegretta committed
2472
   command for this? */
Chris Allegretta's avatar
Chris Allegretta committed
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
char *tail(char *foo)
{
    char *tmp = NULL;

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

    tmp++;

    return tmp;
}

Chris Allegretta's avatar
Chris Allegretta committed
2486
/* Strip one dir from the end of a string */
Chris Allegretta's avatar
Chris Allegretta committed
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
void striponedir(char *foo)
{
    char *tmp = NULL;

    /* 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)
	*tmp = 0;
    else
2505
2506
    { /* SPK may need to make a 'default' path here */
        if (*tmp != '/') *(tmp) = '.';
Chris Allegretta's avatar
Chris Allegretta committed
2507
	*(tmp+1) = 0;
2508
    }
Chris Allegretta's avatar
Chris Allegretta committed
2509
2510
2511
2512

    return;
}

Chris Allegretta's avatar
Chris Allegretta committed
2513
/* Our browser function.  inpath is the path to start browsing from */
Chris Allegretta's avatar
Chris Allegretta committed
2514
2515
2516
2517
2518
2519
2520
char *do_browser(char *inpath)
{
    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;
2521
    int lineno = 0, kb;
Chris Allegretta's avatar
Chris Allegretta committed
2522
    char **filelist = (char **) NULL;
2523
2524
2525
2526
2527
#ifndef DISABLE_MOUSE
#ifdef NCURSES_MOUSE_VERSION
    MEVENT mevent;
#endif
#endif
Chris Allegretta's avatar
Chris Allegretta committed
2528

Chris Allegretta's avatar
Chris Allegretta committed
2529
2530
2531
    /* 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
2532
2533
2534
2535
2536
    if (path != NULL && strcmp(path, inpath)) {
	free(path);
	path = NULL;
    }

Chris Allegretta's avatar
Chris Allegretta committed
2537
    /* if path doesn't exist, make it so */
Chris Allegretta's avatar
Chris Allegretta committed
2538
2539
2540
2541
    if (path == NULL)
	path = mallocstrcpy(path, inpath);

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

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

2547
    kb = keypad_on(edit, 1);
Chris Allegretta's avatar
Chris Allegretta committed
2548
    titlebar(path);
2549
    bottombars(browser_list);
Chris Allegretta's avatar
Chris Allegretta committed
2550
2551
2552
2553
2554
    curs_set(0);
    wmove(edit, 0, 0);
    i = 0;
    width = 0;
    filecols = 0;
Chris Allegretta's avatar
Chris Allegretta committed
2555
2556

    /* Loop invariant: Microsoft sucks. */
Chris Allegretta's avatar
Chris Allegretta committed
2557
    do {
Rocco Corsi's avatar
   
Rocco Corsi committed
2558
2559
2560
2561
	DIR *test_dir;

	blank_statusbar_refresh();

2562
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
2563
	currshortcut = browser_list;
2564
2565
#endif

Chris Allegretta's avatar
Chris Allegretta committed
2566
2567
 	editline = 0;
	col = 0;
2568
	    
2569
	/* Compute line number we're on now, so we don't divide by zero later */
2570
2571
2572
2573
	if (width == 0)
	    lineno = selected;
	else
	    lineno = selected / width;
Chris Allegretta's avatar
Chris Allegretta committed
2574
2575

	switch (kbinput) {
2576

2577
#ifndef DISABLE_MOUSE
2578
2579
#ifdef NCURSES_MOUSE_VERSION
        case KEY_MOUSE:
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
	    if (getmouse(&mevent) == ERR)
	        return retval;
 
	    /* If they clicked in the edit window, they probably clicked
		on a file */
 	    if (wenclose(edit, mevent.y, mevent.x)) { 
		int selectedbackup = selected;

		mevent.y -= 2;

		/* If we're on line 0, don't toy with finding out what
			page we're on */
		if (lineno / editwinrows == 0)
		    selected = mevent.y * width + mevent.x / longest;
		else
		    selected = (lineno / editwinrows) * editwinrows * width 
			+ mevent.y * width + mevent.x / longest;

		/* If we're off the screen, reset to the last item.
		   If we clicked where we did last time, select this name! */
2600
		if (selected > numents - 1)
2601
2602
2603
2604
2605
2606
2607
2608
		    selected = numents - 1;
		else if (selectedbackup == selected) {
		    ungetch('s');	/* Unget the 'select' key */
		    break;
		}
	    } else	/* Must be clicking a shortcut */
		do_mouse();

2609
2610
2611
            break;
#endif
#endif
2612
	case NANO_UP_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2613
2614
2615
2616
2617
	case KEY_UP:
	case 'u':
	    if (selected - width >= 0)
		selected -= width;
	    break;
2618
	case NANO_BACK_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2619
	case KEY_LEFT:
2620
2621
	case NANO_BACKSPACE_KEY:
	case 127:
Chris Allegretta's avatar
Chris Allegretta committed
2622
2623
2624
2625
2626
	case 'l':
	    if (selected > 0)
		selected--;
	    break;
	case KEY_DOWN:
2627
	case NANO_DOWN_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2628
2629
2630
2631
2632
	case 'd':
	    if (selected + width <= numents - 1)
		selected += width;
	    break;
	case KEY_RIGHT:
2633
	case NANO_FORWARD_KEY:
Chris Allegretta's avatar
Chris Allegretta committed
2634
2635
2636
2637
2638
	case 'r':
	    if (selected < numents - 1)
		selected++;
	    break;
	case NANO_PREVPAGE_KEY:
2639
	case NANO_PREVPAGE_FKEY:
Chris Allegretta's avatar
Chris Allegretta committed
2640
	case KEY_PPAGE:
2641
	case '-':
2642
2643

	    if (lineno % editwinrows == 0) {
2644
2645
		if (selected - (editwinrows * width) >= 0)
		    selected -= editwinrows * width; 
Chris Allegretta's avatar
Chris Allegretta committed
2646
2647
2648
2649
		else
		    selected = 0;
	    }
	    else if (selected - (editwinrows + 
2650
2651
2652
		lineno % editwinrows) * width  >= 0)

		selected -= (editwinrows + lineno % editwinrows) * width; 
Chris Allegretta's avatar
Chris Allegretta committed
2653
2654
2655
2656
	    else
		selected = 0;
	    break;
	case NANO_NEXTPAGE_KEY:
2657
	case NANO_NEXTPAGE_FKEY:
Chris Allegretta's avatar
Chris Allegretta committed
2658
	case KEY_NPAGE:	
2659
	case ' ':
2660
	    if (lineno % editwinrows == 0) {
2661
2662
		if (selected + (editwinrows * width) <= numents - 1)
		    selected += editwinrows * width; 
Chris Allegretta's avatar
Chris Allegretta committed
2663
2664
2665
2666
		else
		    selected = numents - 1;
	    }
	    else if (selected + (editwinrows - 
2667
2668
			lineno %  editwinrows) * width <= numents - 1)
 		selected += (editwinrows - lineno % editwinrows) * width; 
Chris Allegretta's avatar
Chris Allegretta committed
2669
2670
2671
 	    else
		selected = numents - 1;
	    break;
2672
2673
2674
2675
	case NANO_HELP_KEY:
	case NANO_HELP_FKEY:
	     do_help();
	     break;
Chris Allegretta's avatar
Chris Allegretta committed
2676
	case KEY_ENTER:
2677
	case NANO_ENTER_KEY:
2678
2679
	case 's': /* More Pico compatibility */
	case 'S':
Chris Allegretta's avatar
Chris Allegretta committed
2680
2681

	    /* You can't cd up from / */
Rocco Corsi's avatar
   
Rocco Corsi committed
2682
	    if (!strcmp(filelist[selected], "/..") && !strcmp(path, "/")) {
Chris Allegretta's avatar
Chris Allegretta committed
2683
		statusbar(_("Can't move up a directory"));
Rocco Corsi's avatar
   
Rocco Corsi committed
2684
2685
2686
2687
		break;
	    }

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

2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
#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

2702
2703
2704
	    /* SPK for '.' path, get the current path via getcwd */
	    if (!strcmp(path, "./..")) {
		free(path);
Chris Allegretta's avatar
Chris Allegretta committed
2705
2706
2707
#ifdef PATH_MAX
		path = getcwd(NULL, PATH_MAX + 1);
#else
2708
		path = getcwd(NULL, 0);
Chris Allegretta's avatar
Chris Allegretta committed
2709
#endif
2710
2711
2712
2713
2714
2715
2716
		striponedir(path);		    
		align(&path);
		free_charptrarray(filelist, numents);
		free(foo);
		return do_browser(path);
	    }

Chris Allegretta's avatar
Chris Allegretta committed
2717
2718
	    st = filestat(path);
	    if (S_ISDIR(st.st_mode)) {
Rocco Corsi's avatar
   
Rocco Corsi committed
2719
		if ((test_dir = opendir(path)) == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
2720
		    /* We can't open this dir for some reason.  Complain */
Chris Allegretta's avatar
Chris Allegretta committed
2721
		    statusbar(_("Can't open \"%s\": %s"), path, strerror(errno));
Rocco Corsi's avatar
   
Rocco Corsi committed
2722
		    striponedir(path);
Chris Allegretta's avatar
Chris Allegretta committed
2723
2724
		    align(&path);
		    break;
Chris Allegretta's avatar
Chris Allegretta committed
2725
		} 
Rocco Corsi's avatar
   
Rocco Corsi committed
2726
		closedir(test_dir);
Chris Allegretta's avatar
Chris Allegretta committed
2727
2728

		if (!strcmp("..", tail(path))) {
Chris Allegretta's avatar
Chris Allegretta committed
2729
2730
		    /* They want to go up a level, so strip off .. and the
			current dir */
Chris Allegretta's avatar
Chris Allegretta committed
2731
2732
2733
2734
		    striponedir(path);
		    striponedir(path);
		    align(&path);
		}
Chris Allegretta's avatar
Chris Allegretta committed
2735
2736

		/* Start over again with the new path value */
2737
2738
		free_charptrarray(filelist, numents);
		free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
2739
2740
2741
2742
2743
2744
		return do_browser(path);
	    } else {
		retval = path;
		abort = 1;
	    }
	    break;
Rocco Corsi's avatar
   
Rocco Corsi committed
2745
2746
2747
2748
2749
2750
	/* Goto a specific directory */
	case 'g':	/* Pico compatibility */
	case 'G':
	case NANO_GOTO_KEY:

	    curs_set(1);
2751
2752
	    j = statusq(0, gotodir_list, "", _("Goto Directory"));
	    bottombars(browser_list);
Rocco Corsi's avatar
   
Rocco Corsi committed
2753
2754
	    curs_set(0);

2755
2756
2757
2758
2759
2760
2761
2762
2763
#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
2764
2765
2766
2767
2768
2769
2770
2771
2772
	    if (j < 0) {
		statusbar(_("Goto Cancelled"));
		break;
	    }

	    if (answer[0] != '/') {
		char *saveanswer = NULL;

		saveanswer = mallocstrcpy(saveanswer, answer);
2773
		answer = nrealloc(answer, strlen(path) + strlen(saveanswer) + 2);
Rocco Corsi's avatar
   
Rocco Corsi committed
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
		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
2789
2790
2791
2792
2793
	/* Stuff we want to abort the browser */
	case 'q':
	case 'Q':
	case 'e':	/* Pico compatibility, yeech */
	case 'E':
2794
	case NANO_CANCEL_KEY:
2795
	case NANO_EXIT_FKEY:
Chris Allegretta's avatar
Chris Allegretta committed
2796
2797
2798
2799
2800
2801
		abort = 1;
		break;
	}
	if (abort)
	    break;

Rocco Corsi's avatar
   
Rocco Corsi committed
2802
2803
	blank_edit();

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

Chris Allegretta's avatar
Chris Allegretta committed
2848
	    /* Hilight the currently selected file/dir */
2849
	    if (j == selected) {
Chris Allegretta's avatar
Chris Allegretta committed
2850
		wattron(edit, A_REVERSE);
2851
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2852
	    waddnstr(edit, foo, strlen(foo));
2853
	    if (j == selected) {
Chris Allegretta's avatar
Chris Allegretta committed
2854
		wattroff(edit, A_REVERSE);
2855
	    }
Chris Allegretta's avatar
Chris Allegretta committed
2856

Chris Allegretta's avatar
Chris Allegretta committed
2857
	    /* And add some space between the cols */
Chris Allegretta's avatar
Chris Allegretta committed
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
	    waddstr(edit, "  ");
	    col += 2;

	    /* And if the next entry isn't going to fit on the
		line, move to the next one */
	    if (col > (COLS - longest)) {
		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
2875
    titlebar(NULL);
Chris Allegretta's avatar
Chris Allegretta committed
2876
    edit_refresh();
2877
    kb = keypad_on(edit, kb);
Chris Allegretta's avatar
Chris Allegretta committed
2878

Chris Allegretta's avatar
Chris Allegretta committed
2879
    /* cleanup */
Chris Allegretta's avatar
Chris Allegretta committed
2880
2881
2882
2883
    free_charptrarray(filelist, numents);
    free(foo);
    return retval;
}
2884

2885
/* Browser front end, checks to see if inpath has a dir in it and, if so,
2886
2887
2888
2889
2890
2891
2892
2893
 starts do_browser from there, else from the current dir */
char *do_browse_from(char *inpath)
{
    struct stat st;
    char *tmp = NULL;

    tmp = mallocstrcpy(tmp, inpath);

2894

2895
    /* If there's no / in the string, we may as well start from . */
2896
2897
    if (tmp == NULL || *tmp == '\0' || !strstr(tmp, "/")) {
#ifdef PATH_MAX
Chris Allegretta's avatar
Chris Allegretta committed
2898
	char *from = getcwd(NULL, PATH_MAX + 1);
2899
#else
2900
	char *from = getcwd(NULL, 0);
2901
2902
2903
#endif /* PATH_MAX */
	return do_browser(from ? from : "./");
    }
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916

    /* If the string is a directory, pass do_browser that */
    st = filestat(tmp);
    if (S_ISDIR(st.st_mode))
	return do_browser(tmp);

    /* Okay, there's a dir in there, but not at the end of the string... 
       try stripping it off */
    striponedir(tmp);
    align(&tmp);
    return do_browser(tmp);

}
Chris Allegretta's avatar
Chris Allegretta committed
2917
#endif /* !DISABLE_BROWSER */