files.c 24.5 KB
Newer Older
Chris Allegretta's avatar
Chris Allegretta committed
1
/* $Id$ */
Chris Allegretta's avatar
Chris Allegretta committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**************************************************************************
 *   files.c                                                              *
 *                                                                        *
 *   Copyright (C) 1999 Chris Allegretta                                  *
 *   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 *
 *   the Free Software Foundation; either version 1, or (at your option)  *
 *   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.            *
 *                                                                        *
 **************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
Chris Allegretta's avatar
Chris Allegretta committed
30
31
#include <ctype.h>
#include <dirent.h>
Chris Allegretta's avatar
Chris Allegretta committed
32
33
34
35

#include "config.h"
#include "proto.h"
#include "nano.h"
Chris Allegretta's avatar
Chris Allegretta committed
36

Chris Allegretta's avatar
Chris Allegretta committed
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#ifndef NANO_SMALL
#include <libintl.h>
#define _(string) gettext(string)
#else
#define _(string) (string)
#endif

/* Load file into edit buffer - takes data from file struct */
void load_file(void)
{
    current = fileage;
    wmove(edit, current_y, current_x);
}

/* What happens when there is no file to open? aiee! */
void new_file(void)
{
    fileage = nmalloc(sizeof(filestruct));
    fileage->data = nmalloc(1);
    strcpy(fileage->data, "");
    fileage->prev = NULL;
    fileage->next = NULL;
    fileage->lineno = 1;
    filebot = fileage;
    edittop = fileage;
    editbot = fileage;
    current = fileage;
    totlines = 1;
    UNSET(VIEW_MODE);
}


int read_byte(int fd, char *filename, char *input)
{
    static char buf[BUFSIZ];
    static int index = 0;
    static int size = 0;

    if (index == size) {
	index = 0;
	size = read(fd, buf, BUFSIZ);
	if (size == -1) {
	    clear();
	    refresh();
	    resetty();
	    endwin();
	    perror(filename);
	}
	if (!size)
	    return 0;
    }
    *input = buf[index++];
    return 1;
}

filestruct *read_line(char *buf, filestruct * prev, int *line1ins)
{
    filestruct *fileptr;

    fileptr = nmalloc(sizeof(filestruct));
    fileptr->data = nmalloc(strlen(buf) + 2);
    strcpy(fileptr->data, buf);

    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;
}


int read_file(int fd, char *filename)
{
131
    long size, num_lines = 0, linetemp = 0;
Chris Allegretta's avatar
Chris Allegretta committed
132
133
134
135
136
137
138
    char input[2];		/* buffer */
    char *buf;
    long i = 0, bufx = 128;
    filestruct *fileptr = current, *tmp = NULL;
    int line1ins = 0;

    buf = nmalloc(bufx);
139
    buf[0] = '\0';
Chris Allegretta's avatar
Chris Allegretta committed
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

    if (fileptr != NULL && fileptr->prev != NULL) {
	fileptr = fileptr->prev;
	tmp = fileptr;
    } else if (fileptr != NULL && fileptr->prev == NULL) {
	tmp = fileage;
	current = fileage;
	line1ins = 1;
    }
    input[1] = 0;
    /* Read the entire file into file struct */
    while ((size = read_byte(fd, filename, input)) > 0) {
	linetemp = 0;
	if (input[0] == '\n') {
	    fileptr = read_line(buf, fileptr, &line1ins);
155
	    num_lines++;
Chris Allegretta's avatar
Chris Allegretta committed
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
	    buf[0] = 0;
	    i = 0;
	} 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
	       decrease it at all.  We do free it at the end though. */

	    if (i >= bufx - 1) {
		buf = nrealloc(buf, bufx + 128);
		bufx += 128;
	    }
	    buf[i] = input[0];
	    buf[i + 1] = 0;
	    i++;
	}
	totsize += size;
    }

    /* Did we not get a newline but still have stuff to do? */
    if (buf[0]) {
	fileptr = read_line(buf, fileptr, &line1ins);
178
	num_lines++;
Chris Allegretta's avatar
Chris Allegretta committed
179
180
181
	buf[0] = 0;
    }
    /* Did we even GET a file? */
182
    if (totsize == 0 || fileptr == NULL) {
Chris Allegretta's avatar
Chris Allegretta committed
183
	new_file();
184
	statusbar(_("Read %d lines"), num_lines);
Chris Allegretta's avatar
Chris Allegretta committed
185
186
	return 1;
    }
Robert Siemborski's avatar
Robert Siemborski committed
187

Chris Allegretta's avatar
Chris Allegretta committed
188
189
190
191
192
193
194
195
    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
196
	new_magicline();
Chris Allegretta's avatar
Chris Allegretta committed
197
	totsize--;
Robert Siemborski's avatar
Robert Siemborski committed
198
199

	/* Update the edit buffer */
Chris Allegretta's avatar
Chris Allegretta committed
200
201
	load_file();
    }
202
203
    statusbar(_("Read %d lines"), num_lines);
    totlines += num_lines;
Chris Allegretta's avatar
Chris Allegretta committed
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233

    free(buf);
    close(fd);

    return 1;
}

/* Open the file (and decide if it exists) */
int open_file(char *filename, int insert, int quiet)
{
    int fd;
    struct stat fileinfo;

    if (!strcmp(filename, "") || stat(filename, &fileinfo) == -1) {
	if (insert) {
	    if (!quiet)
		statusbar(_("\"%s\" not found"), filename);
	    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);
	return -1;
    } else {			/* File is A-OK */
	if (S_ISDIR(fileinfo.st_mode)) {
	    statusbar(_("File \"%s\" is a directory"), filename);
Chris Allegretta's avatar
Chris Allegretta committed
234
235
	    if (!insert)
		new_file();
Chris Allegretta's avatar
Chris Allegretta committed
236
237
238
239
240
241
242
243
244
245
246
247
248
	    return -1;
	}
	if (!quiet)
	    statusbar(_("Reading File"));
	read_file(fd, filename);
    }

    return 1;
}

int do_insertfile(void)
{
    int i;
249
    char *realname = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
250
251

    wrap_reset();
252
    i = statusq(1, writefile_list, WRITEFILE_LIST_LEN, "",
Chris Allegretta's avatar
Chris Allegretta committed
253
254
255
256
257
258
259
		_("File to insert [from ./] "));
    if (i != -1) {

#ifdef DEBUG
	fprintf(stderr, "filename is %s", answer);
#endif

260
#ifndef DISABLE_TABCOMP
261
	realname = real_dir_from_tilde(answer);
262
#else
263
	realname = mallocstrcpy(realname, answer);
264
#endif
265
266
267

	i = open_file(realname, 1, 0);
	free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
268
269
270
271
272

	dump_buffer(fileage);
	set_modified();

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

	/* If we've gone off the bottom, recenter, otherwise just redraw */
Chris Allegretta's avatar
Chris Allegretta committed
276
	if (current->lineno > editbot->lineno)
277
	    edit_update(current, CENTER);
Chris Allegretta's avatar
Chris Allegretta committed
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
	else
	    edit_refresh();

	UNSET(KEEP_CUTBUFFER);
	display_main_list();
	return i;
    } else {
	statusbar(_("Cancelled"));
	UNSET(KEEP_CUTBUFFER);
	display_main_list();
	return 0;
    }
}

/*
 * Write a file out.  If tmp is nonzero, we set the umask to 0600,
 * we don't set the global variable filename to it's name, and don't
 * print out how many lines we wrote on the statusbar.
 * 
297
298
 * tmp means we are writing a tmp file in a secute fashion.  We use
 * it when spell checking or dumping the file on an error.
Chris Allegretta's avatar
Chris Allegretta committed
299
300
301
302
303
304
 */
int write_file(char *name, int tmp)
{
    long size, lineswritten = 0;
    char buf[PATH_MAX + 1];
    filestruct *fileptr;
305
    int fd, mask = 0, realexists, anyexists;
306
    struct stat st, lst, st2;
307
    static char *realname = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
308
309
310
311
312
313
314

    if (!strcmp(name, "")) {
	statusbar(_("Cancelled"));
	return -1;
    }
    titlebar();
    fileptr = fileage;
315
316
317
318

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

319
320
321
322
323
#ifndef DISABLE_TABCOMP
    realname = real_dir_from_tilde(name);
#else
    realname = mallocstrcpy(realname, name);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
324

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

328
329
    /* Stat the link itself for the check... */
    anyexists = lstat(realname, &lst);
330

331
332
    /* New case: if the file exists, just give up */
    if (tmp && anyexists != -1)
333
	return -1;
334
    /* NOTE: If you change this statement, you MUST CHANGE the if 
335
336
337
       statement below (that starts "if ((!ISSET(FOLLOW_SYMLINKS)...")
       to reflect whether or not to link/unlink/rename the file */
    else if (ISSET(FOLLOW_SYMLINKS) || !S_ISLNK(lst.st_mode) || tmp) {
Chris Allegretta's avatar
Chris Allegretta committed
338
	/* Use O_EXCL if tmp == 1.  This is now copied from joe, because
339
340
341
342
343
	   wiggy says so *shrug*. */
	if (tmp)
	    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));
344
345

	/* First, just give up if we couldn't even open the file */
346
	if (fd == -1) {
347
	    if (!tmp && ISSET(TEMP_OPT)) {
Chris Allegretta's avatar
Chris Allegretta committed
348
349
350
		UNSET(TEMP_OPT);
		return do_writeout(1);
	    }
Chris Allegretta's avatar
Chris Allegretta committed
351
352
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
353
	    free(realname);
Chris Allegretta's avatar
Chris Allegretta committed
354
355
	    return -1;
	}
356
357
358
359

	/* Now we fstat() the file, to make sure it's the same file still!
	   Thanks to Oliver Friedrichs(?) for this code from securityfocus */

360
	if (fstat(fd, &st2) != 0) {
361
	    close(fd);
362
	    return -1;
363
	}
364

Chris Allegretta's avatar
Chris Allegretta committed
365
366
367
    }
    /* Don't follow symlink.  Create new file. */
    else {
368
	if (strlen(realname) > (PATH_MAX - 7)) {
Chris Allegretta's avatar
Chris Allegretta committed
369
370
371
372
373
	    statusbar(_("Could not open file: Path length exceeded."));
	    return -1;
	}

	memset(buf, 0x00, PATH_MAX + 1);
374
	strcat(buf, realname);
Chris Allegretta's avatar
Chris Allegretta committed
375
376
	strcat(buf, ".XXXXXX");
	if ((fd = mkstemp(buf)) == -1) {
Chris Allegretta's avatar
Chris Allegretta committed
377
378
379
	    if (ISSET(TEMP_OPT)) {
		UNSET(TEMP_OPT);
		return do_writeout(1);
380
	    }
Chris Allegretta's avatar
Chris Allegretta committed
381
382
383
384
385
386
387
388
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
	    return -1;
	}
    }

    dump_buffer(fileage);
    while (fileptr != NULL && fileptr->next != NULL) {
Robert Siemborski's avatar
Robert Siemborski committed
389
	/* Next line is so we discount the "magic line" */
390
391
	if (filebot == fileptr && fileptr->data[0] == '\0')
	    break;
Robert Siemborski's avatar
Robert Siemborski committed
392

Chris Allegretta's avatar
Chris Allegretta committed
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
	size = write(fd, fileptr->data, strlen(fileptr->data));
	if (size == -1) {
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
	    return -1;
	} else {
#ifdef DEBUG
	    fprintf(stderr, _("Wrote >%s\n"), fileptr->data);
#endif
	}
	write(fd, "\n", 1);

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

    if (fileptr != NULL) {
	size = write(fd, fileptr->data, strlen(fileptr->data));
	if (size == -1) {
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
	    return -1;
	} else if (size > 0) {
	    size = write(fd, "\n", 1);
	    if (size == -1) {
		statusbar(_("Could not open file for writing: %s"),
			  strerror(errno));
		return -1;
	    }
	}
    }


    if (close(fd) == -1) {
427
	statusbar(_("Could not close %s: %s"), realname, strerror(errno));
Chris Allegretta's avatar
Chris Allegretta committed
428
429
430
431
	unlink(buf);
	return -1;
    }

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

435
436
437
	/* Use default umask as file permisions if file is a new file. */
	mask = umask(0);
	umask(mask);
Chris Allegretta's avatar
Chris Allegretta committed
438

439
440
441
442
443
	if (tmp)		/* We don't want anyone reading our temporary file! */
	    mask = 0600;
	else
	    mask = 0666 & ~mask;
    } else
Chris Allegretta's avatar
Chris Allegretta committed
444
	/* Use permissions from file we are overwriting. */
445
446
447
448
449
	mask = st.st_mode;

    if (!tmp && (!ISSET(FOLLOW_SYMLINKS) && S_ISLNK(lst.st_mode))) {
	if (unlink(realname) == -1) {
	    if (errno != ENOENT) {
450
		statusbar(_("Could not open %s for writing: %s"),
451
			  realname, strerror(errno));
452
453
454
		unlink(buf);
		return -1;
	    }
Chris Allegretta's avatar
Chris Allegretta committed
455
	}
456
457
458
459
460
461
462
463
464
465
466
467
468
	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
469
    }
470
471
472
473
    if (chmod(realname, mask) == -1)
	statusbar(_("Could not set permissions %o on %s: %s"),
		  mask, realname, strerror(errno));

Chris Allegretta's avatar
Chris Allegretta committed
474
    if (!tmp) {
475
	strncpy(filename, realname, 132);
Chris Allegretta's avatar
Chris Allegretta committed
476
	statusbar(_("Wrote %d lines"), lineswritten);
477
478
	UNSET(MODIFIED);
	titlebar();
Chris Allegretta's avatar
Chris Allegretta committed
479
480
481
482
483
484
485
    }
    return 1;
}

int do_writeout(int exiting)
{
    int i = 0;
486
487
488
#ifdef NANO_EXTRA
    static int did_cred = 0;
#endif
Chris Allegretta's avatar
Chris Allegretta committed
489

490
    answer = mallocstrcpy(answer, filename);
Chris Allegretta's avatar
Chris Allegretta committed
491

492
    if ((exiting) && (ISSET(TEMP_OPT))) {
493
	if (filename[0]) {
494
495
496
	    i = write_file(answer, 0);
	    display_main_list();
	    return i;
497
	} else {
Chris Allegretta's avatar
Chris Allegretta committed
498
499
500
501
502
	    UNSET(TEMP_OPT);
	    do_exit();

	    /* They cancelled, abort quit */
	    return -1;
503
	}
Chris Allegretta's avatar
Chris Allegretta committed
504
505
506
    }

    while (1) {
507
	i = statusq(1, writefile_list, WRITEFILE_LIST_LEN, answer,
Chris Allegretta's avatar
Chris Allegretta committed
508
		    _("File Name to write"));
Chris Allegretta's avatar
Chris Allegretta committed
509

Chris Allegretta's avatar
Chris Allegretta committed
510
	if (i != -1) {
Chris Allegretta's avatar
Chris Allegretta committed
511
512
513
514

#ifdef DEBUG
	    fprintf(stderr, _("filename is %s"), answer);
#endif
515
516

#ifdef NANO_EXTRA
517
518
	    if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy")
		&& !did_cred) {
519
520
		do_credits();
		did_cred = 1;
521
		return -1;
522
523
	    }
#endif
524
	    if (strcmp(answer, filename)) {
Chris Allegretta's avatar
Chris Allegretta committed
525
526
527
528
529
530
		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
531
532
		}
	    }
Chris Allegretta's avatar
Chris Allegretta committed
533
534
535
536
	    i = write_file(answer, 0);

	    display_main_list();
	    return i;
Chris Allegretta's avatar
Chris Allegretta committed
537
	} else {
Chris Allegretta's avatar
Chris Allegretta committed
538
539
540
	    statusbar(_("Cancelled"));
	    display_main_list();
	    return 0;
Chris Allegretta's avatar
Chris Allegretta committed
541
	}
Chris Allegretta's avatar
Chris Allegretta committed
542
543
544
545
546
547
548
    }
}

int do_writeout_void(void)
{
    return do_writeout(0);
}
Chris Allegretta's avatar
Chris Allegretta committed
549

550
551
552
553
554
555
#ifndef DISABLE_TABCOMP
static char **homedirs;

/* Return a malloc()ed string containing the actual directory, used
 * to convert ~user and ~/ notation...
 */
556
char *real_dir_from_tilde(char *buf)
557
{
558
559
    char *dirtmp = NULL, *line = NULL, byte[1], *lineptr;
    int fd, i, status, searchctr = 1;
560
561

    if (buf[0] == '~') {
562
563
564
	if (buf[1] == '~')
	    goto abort;		/* Handle ~~ without segfaulting =) */
	else if (buf[1] == '/') {
565
566
567
568
569
	    if (getenv("HOME") != NULL) {
		dirtmp = nmalloc(strlen(buf) + 2 + strlen(getenv("HOME")));

		sprintf(dirtmp, "%s/%s", getenv("HOME"), &buf[2]);
	    }
570
	} else if (buf[1] != 0) {
571

572
	    if ((fd = open("/etc/passwd", O_RDONLY)) == -1)
573
574
575
576
		goto abort;

	    /* Figure how how much of of the str we need to compare */
	    for (searchctr = 1; buf[searchctr] != '/' &&
577
		 buf[searchctr] != 0; searchctr++);
578
579
580
581

	    do {
		i = 0;
		line = nmalloc(1);
582
583
		while ((status = read(fd, byte, 1)) != 0
		       && byte[0] != '\n') {
584
585
586

		    line[i] = byte[0];
		    i++;
587
		    line = nrealloc(line, i + 1);
588
589
590
591
		}
		line[i] = 0;

		if (i == 0)
592
		    goto abort;
593
594
595
596
597
598
599
600
601
602
603
604
605

		line[i] = 0;
		lineptr = strtok(line, ":");

		if (!strncmp(lineptr, &buf[1], searchctr - 1)) {

		    /* Okay, skip to the password portion now */
		    for (i = 0; i <= 4 && lineptr != NULL; i++)
			lineptr = strtok(NULL, ":");

		    if (lineptr == NULL)
			goto abort;

606
		    /* Else copy the new string into the new buf */
607
608
609
610
611
612
		    dirtmp = nmalloc(strlen(buf) + 2 + strlen(lineptr));

		    sprintf(dirtmp, "%s%s", lineptr, &buf[searchctr]);
		    free(line);
		    break;
		}
613

614
615
616
		free(line);

	    } while (status != 0);
617
	}
618
    } else
619
620
	dirtmp = mallocstrcpy(dirtmp, buf);

621
    return dirtmp;
622

623
  abort:
624
625
    dirtmp = mallocstrcpy(dirtmp, buf);
    return dirtmp;
626
627
628
}

/* Tack a slash onto the string we're completing if it's a directory */
629
int append_slash_if_dir(char *buf, int *lastWasTab, int *place)
630
631
632
{
    char *dirptr;
    struct stat fileinfo;
633
    int ret = 0;
634
635
636
637

    dirptr = real_dir_from_tilde(buf);

    if (stat(dirptr, &fileinfo) == -1)
638
	ret = 0;
639
640
641
642
643
    else if (S_ISDIR(fileinfo.st_mode)) {
	strncat(buf, "/", 1);
	*place += 1;
	/* now we start over again with # of tabs so far */
	*lastWasTab = 0;
644
	ret = 1;
645
646
647
    }

    if (dirptr != buf)
648
	free(dirptr);
649
650

    return ret;
651
}
Chris Allegretta's avatar
Chris Allegretta committed
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673

/*
 * 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)
{
674
675
    char **matches = (char **) NULL, *line = NULL, *lineptr;
    char *matchline = NULL, *matchdir = NULL;
676

677
678
679
    int fd, i = 0, status = 1;
    char byte[1];

680
    if ((fd = open("/etc/passwd", O_RDONLY)) == -1) {
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
	return NULL;
    }

    if (homedirs != NULL) {
	for (i = 0; i < *num_matches; i++)
	    free(homedirs[i]);
	free(homedirs);
	homedirs = (char **) NULL;
	*num_matches = 0;
    }
    matches = nmalloc(BUFSIZ);
    homedirs = nmalloc(BUFSIZ);
    strcat(buf, "*");
    do {
	i = 0;
	line = nmalloc(1);
	while ((status = read(fd, byte, 1)) != 0 && byte[0] != '\n') {

	    line[i] = byte[0];
	    i++;
701
	    line = nrealloc(line, i + 1);
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
	}

	if (i == 0)
	    break;

	line[i] = 0;
	lineptr = strtok(line, ":");

	if (check_wildcard_match(line, &buf[1]) == TRUE) {

	    if (*num_matches == BUFSIZ)
		break;

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
	    matchline = nmalloc(strlen(line) + 2);
	    sprintf(matchline, "~%s", line);

	    for (i = 0; i <= 4 && lineptr != NULL; i++)
		lineptr = strtok(NULL, ":");

	    if (lineptr == NULL)
		break;

	    matchdir = mallocstrcpy(matchdir, lineptr);
	    homedirs[*num_matches] = matchdir;
	    matches[*num_matches] = matchline;

	    ++*num_matches;

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
	}

	free(line);

    } while (status != 0);

    close(fd);
    return matches;
Chris Allegretta's avatar
Chris Allegretta committed
744
745
746
747
748
749
750
751
752
753
754
#ifdef DEBUG
    fprintf(stderr, "\nin username_tab_completion\n");
#endif
    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)
{
755
    char *dirName, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
Chris Allegretta's avatar
Chris Allegretta committed
756
757
758
759
    char **matches = (char **) NULL;
    DIR *dir;
    struct dirent *next;

760
    matches = nmalloc(BUFSIZ);
Chris Allegretta's avatar
Chris Allegretta committed
761
762
763
764

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

765
    /* Okie, if there's a / in the buffer, strip out the directory part */
Chris Allegretta's avatar
Chris Allegretta committed
766
767
768
769
    if (strcmp(buf, "") && strstr(buf, "/")) {
	dirName = malloc(strlen(buf) + 1);
	tmp = buf + strlen(buf);
	while (*tmp != '/' && tmp != buf)
770
	    tmp--;
771

Chris Allegretta's avatar
Chris Allegretta committed
772
773
	tmp++;

774
775
776
	strncpy(dirName, buf, tmp - buf + 1);
	dirName[tmp - buf] = 0;

Chris Allegretta's avatar
Chris Allegretta committed
777
778
779
780
781
782
783
784
785
786
787
788
    } else {
	if ((dirName = getcwd(NULL, 0)) == NULL)
	    return matches;
	else
	    tmp = buf;
    }

#ifdef DEBUG
    fprintf(stderr, "\nDir = %s\n", dirName);
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif
789

790
791
792
793
794
795
796
797
798
799
800
    dirtmp = real_dir_from_tilde(dirName);
    free(dirName);
    dirName = dirtmp;

#ifdef DEBUG
    fprintf(stderr, "\nDir = %s\n", dirName);
    fprintf(stderr, "\nbuf = %s\n", buf);
    fprintf(stderr, "\ntmp = %s\n", tmp);
#endif


Chris Allegretta's avatar
Chris Allegretta committed
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
    dir = opendir(dirName);
    if (!dir) {
	/* Don't print an error, just shut up and return */
	*num_matches = 0;
	beep();
	return (matches);
    }
    while ((next = readdir(dir)) != NULL) {

	/* Some quick sanity checks */
	if ((strcmp(next->d_name, "..") == 0)
	    || (strcmp(next->d_name, ".") == 0)) {
	    continue;
	}
#ifdef DEBUG
816
	fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta's avatar
Chris Allegretta committed
817
818
819
#endif
	/* See if this matches */
	if (check_wildcard_match(next->d_name, tmp) == TRUE) {
820
821
822
823
824
825
826
827

	    /* Cool, found a match.  Add it to the list
	     * This makes a lot more sense to me (Chris) this way...
	     */
	    tmp2 = NULL;
	    tmp2 = nmalloc(strlen(next->d_name) + 1);
	    strcpy(tmp2, next->d_name);
	    matches[*num_matches] = tmp2;
Chris Allegretta's avatar
Chris Allegretta committed
828
	    ++*num_matches;
829
830
831
832

	    /* If there's no more room, bail out */
	    if (*num_matches == BUFSIZ)
		break;
Chris Allegretta's avatar
Chris Allegretta committed
833
834
835
836
837
838
	}
    }

    return (matches);
}

839
/* This function now has an arg which refers to how much the 
Chris Allegretta's avatar
Chris Allegretta committed
840
841
 * statusbar (place) should be advanced, i.e. the new cursor pos.
 */
842
char *input_tab(char *buf, int place, int *lastWasTab, int *newplace)
Chris Allegretta's avatar
Chris Allegretta committed
843
844
{
    /* Do TAB completion */
845
    static int num_matches = 0, match_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
846
    static char **matches = (char **) NULL;
847
    int pos = place, i = 0, col = 0, editline = 0;
848
    int longestname = 0, is_dir = 0;
849
    char *foo;
Chris Allegretta's avatar
Chris Allegretta committed
850

851
    if (*lastWasTab == FALSE) {
852
	char *tmp, *copyto, *matchBuf;
Chris Allegretta's avatar
Chris Allegretta committed
853

854
855
	*lastWasTab = 1;

Chris Allegretta's avatar
Chris Allegretta committed
856
857
	/* Make a local copy of the string -- up to the position of the
	   cursor */
858
859
	matchBuf = (char *) calloc(strlen(buf) + 2, sizeof(char));

Chris Allegretta's avatar
Chris Allegretta committed
860
861
862
863
	strncpy(matchBuf, buf, place);
	tmp = matchBuf;

	/* skip any leading white space */
864
	while (*tmp && isspace((int) *tmp))
Chris Allegretta's avatar
Chris Allegretta committed
865
866
867
868
	    ++tmp;

	/* Free up any memory already allocated */
	if (matches != NULL) {
869
870
	    for (i = i; i < num_matches; i++)
		free(matches[i]);
Chris Allegretta's avatar
Chris Allegretta committed
871
872
	    free(matches);
	    matches = (char **) NULL;
873
	    num_matches = 0;
Chris Allegretta's avatar
Chris Allegretta committed
874
875
876
877
878
	}

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

879
	/* FIXME -- this check is broken! */
880
881
	if (*tmp == '~' && !strchr(tmp, '/'))
	    matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta's avatar
Chris Allegretta committed
882
883
884
885
886
887
888
889
890

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

891
892
893
#ifdef DEBUG
	fprintf(stderr, "%d matches found...\n", num_matches);
#endif
Chris Allegretta's avatar
Chris Allegretta committed
894
	/* Did we find exactly one match? */
895
	switch (num_matches) {
896
	case 0:
897
	    blank_edit();
898
	    wrefresh(edit);
899
900
	    break;
	case 1:
901
902
903
904

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

	    if (strcmp(buf, "") && strstr(buf, "/")) {
905
906
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
907
		tmp++;
908
	    } else
909
910
		tmp = buf;

911
912
913
914
915
	    if (!strcmp(tmp, matches[0]))
		is_dir = append_slash_if_dir(buf, lastWasTab, newplace);

	    if (is_dir)
		break;
916
917

	    copyto = tmp;
918
919
	    for (pos = 0; *tmp == matches[0][pos] &&
		 pos <= strlen(matches[0]); pos++)
920
921
		tmp++;

922
	    /* write out the matched name */
923
	    strncpy(copyto, matches[0], strlen(matches[0]) + 1);
924
925
	    *newplace += strlen(matches[0]) - pos;

926
927
928
	    /* Is it a directory? */
	    append_slash_if_dir(buf, lastWasTab, newplace);

929
930
	    break;
	default:
931
	    /* Check to see if all matches share a beginning, and if so
932
	       tack it onto buf and then beep */
933

934
	    if (strcmp(buf, "") && strstr(buf, "/")) {
935
936
		for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
		     tmp--);
937
		tmp++;
938
	    } else
939
940
941
		tmp = buf;

	    for (pos = 0; *tmp == matches[0][pos] && *tmp != 0 &&
942
		 pos <= strlen(matches[0]); pos++)
943
944
		tmp++;

945
946
947
948
949
950
951
952
953
	    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++;
		}
954
955
		if (match_matches == num_matches &&
		    (i == num_matches || matches[i] != 0)) {
956
		    /* All the matches have the same character at pos+1,
957
		       so paste it into buf... */
958
		    buf = nrealloc(buf, strlen(buf) + 2);
959
		    strncat(buf, matches[0] + pos, 1);
960
		    *newplace += 1;
961
		    pos++;
962
		} else {
963
964
965
966
		    beep();
		    break;
		}
	    }
967
	    break;
968
	}
Chris Allegretta's avatar
Chris Allegretta committed
969
970
971
972
973
974
975
976
977
978
    } else {
	/* Ok -- the last char was a TAB.  Since they
	 * just hit TAB again, print a list of all the
	 * available choices... */
	if (matches && num_matches > 0) {

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

979
	    editline = 0;
980

981
982
983
984
985
986
987
988
	    /* 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;

989
990
	    foo = nmalloc(longestname + 5);

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

994
		/* make each filename shown be the same length as the longest
995
		   filename, with two spaces at the end */
996
997
998
999
1000
1001
		snprintf(foo, longestname + 1, matches[i]);
		while (strlen(foo) < longestname)
		    strcat(foo, " ");

		strcat(foo, "  ");

1002
1003
		/* Disable el cursor */
		curs_set(0);
1004
1005
1006
1007
1008
1009
		/* 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 */
1010
		if (col > (COLS - longestname) && matches[i + 1] != NULL) {
1011
1012
		    editline++;
		    wmove(edit, editline, 0);
1013
1014
1015
1016
		    if (editline == editwinrows - 1) {
			waddstr(edit, _("(more)"));
			break;
		    }
Chris Allegretta's avatar
Chris Allegretta committed
1017
1018
1019
		    col = 0;
		}
	    }
1020
	    free(foo);
Chris Allegretta's avatar
Chris Allegretta committed
1021
	    wrefresh(edit);
1022
1023
	} else
	    beep();
Chris Allegretta's avatar
Chris Allegretta committed
1024
1025
1026
1027

    }

    edit_refresh();
1028
1029
    curs_set(1);
    return buf;
Chris Allegretta's avatar
Chris Allegretta committed
1030
}
1031
#endif