blob: 3cc2dfc62d039dde63d00dded8fda8b63cc3ddc8 [file] [log] [blame]
Chris Allegretta11b00112000-08-06 21:13:45 +00001/* $Id$ */
Chris Allegrettabceb1b22000-06-19 04:22:15 +00002/**************************************************************************
3 * files.c *
4 * *
5 * Copyright (C) 1999 Chris Allegretta *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 1, or (at your option) *
9 * any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
19 * *
20 **************************************************************************/
21
22#include <stdlib.h>
23#include <string.h>
24#include <stdio.h>
25#include <unistd.h>
26#include <sys/types.h>
27#include <sys/stat.h>
28#include <fcntl.h>
29#include <errno.h>
Chris Allegretta04d848e2000-11-05 17:54:41 +000030#include <ctype.h>
31#include <dirent.h>
Chris Allegrettabceb1b22000-06-19 04:22:15 +000032
33#include "config.h"
34#include "proto.h"
35#include "nano.h"
Chris Allegretta4da1fc62000-06-21 03:00:43 +000036
Chris Allegrettabceb1b22000-06-19 04:22:15 +000037#ifndef NANO_SMALL
38#include <libintl.h>
39#define _(string) gettext(string)
40#else
41#define _(string) (string)
42#endif
43
44/* Load file into edit buffer - takes data from file struct */
45void load_file(void)
46{
47 current = fileage;
48 wmove(edit, current_y, current_x);
49}
50
51/* What happens when there is no file to open? aiee! */
52void new_file(void)
53{
54 fileage = nmalloc(sizeof(filestruct));
55 fileage->data = nmalloc(1);
56 strcpy(fileage->data, "");
57 fileage->prev = NULL;
58 fileage->next = NULL;
59 fileage->lineno = 1;
60 filebot = fileage;
61 edittop = fileage;
62 editbot = fileage;
63 current = fileage;
64 totlines = 1;
65 UNSET(VIEW_MODE);
66}
67
68
69int read_byte(int fd, char *filename, char *input)
70{
71 static char buf[BUFSIZ];
72 static int index = 0;
73 static int size = 0;
74
75 if (index == size) {
76 index = 0;
77 size = read(fd, buf, BUFSIZ);
78 if (size == -1) {
79 clear();
80 refresh();
81 resetty();
82 endwin();
83 perror(filename);
84 }
85 if (!size)
86 return 0;
87 }
88 *input = buf[index++];
89 return 1;
90}
91
92filestruct *read_line(char *buf, filestruct * prev, int *line1ins)
93{
94 filestruct *fileptr;
95
96 fileptr = nmalloc(sizeof(filestruct));
97 fileptr->data = nmalloc(strlen(buf) + 2);
98 strcpy(fileptr->data, buf);
99
100 if (*line1ins) {
101 /* Special case, insert with cursor on 1st line. */
102 fileptr->prev = NULL;
103 fileptr->next = fileage;
104 fileptr->lineno = 1;
105 *line1ins = 0;
106 /* If we're inserting into the first line of the file, then
107 we want to make sure that our edit buffer stays on the
108 first line (and that fileage stays up to date!) */
109 fileage = fileptr;
110 edittop = fileptr;
111 } else if (fileage == NULL) {
112 fileage = fileptr;
113 fileage->lineno = 1;
114 fileage->next = fileage->prev = NULL;
115 fileptr = filebot = fileage;
116 } else if (prev) {
117 fileptr->prev = prev;
118 fileptr->next = NULL;
119 fileptr->lineno = prev->lineno + 1;
120 prev->next = fileptr;
121 } else {
122 die(_("read_line: not on first line and prev is NULL"));
123 }
124
125 return fileptr;
126}
127
128
129int read_file(int fd, char *filename)
130{
Adam Rogoyski1e328fb2000-07-08 03:57:16 +0000131 long size, num_lines = 0, linetemp = 0;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000132 char input[2]; /* buffer */
133 char *buf;
134 long i = 0, bufx = 128;
135 filestruct *fileptr = current, *tmp = NULL;
136 int line1ins = 0;
137
138 buf = nmalloc(bufx);
Chris Allegretta8f6c0692000-07-19 01:16:18 +0000139 buf[0] = '\0';
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000140
141 if (fileptr != NULL && fileptr->prev != NULL) {
142 fileptr = fileptr->prev;
143 tmp = fileptr;
144 } else if (fileptr != NULL && fileptr->prev == NULL) {
145 tmp = fileage;
146 current = fileage;
147 line1ins = 1;
148 }
149 input[1] = 0;
150 /* Read the entire file into file struct */
151 while ((size = read_byte(fd, filename, input)) > 0) {
152 linetemp = 0;
153 if (input[0] == '\n') {
154 fileptr = read_line(buf, fileptr, &line1ins);
Adam Rogoyski1e328fb2000-07-08 03:57:16 +0000155 num_lines++;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000156 buf[0] = 0;
157 i = 0;
158 } else {
159 /* Now we allocate a bigger buffer 128 characters at a time.
160 If we allocate a lot of space for one line, we may indeed
161 have to use a buffer this big later on, so we don't
162 decrease it at all. We do free it at the end though. */
163
164 if (i >= bufx - 1) {
165 buf = nrealloc(buf, bufx + 128);
166 bufx += 128;
167 }
168 buf[i] = input[0];
169 buf[i + 1] = 0;
170 i++;
171 }
172 totsize += size;
173 }
174
175 /* Did we not get a newline but still have stuff to do? */
176 if (buf[0]) {
177 fileptr = read_line(buf, fileptr, &line1ins);
Adam Rogoyski1e328fb2000-07-08 03:57:16 +0000178 num_lines++;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000179 buf[0] = 0;
180 }
181 /* Did we even GET a file? */
Chris Allegretta9956e532000-11-26 02:09:53 +0000182 if (totsize == 0 || fileptr == NULL) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000183 new_file();
Adam Rogoyski1e328fb2000-07-08 03:57:16 +0000184 statusbar(_("Read %d lines"), num_lines);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000185 return 1;
186 }
Robert Siemborski63b3d7e2000-07-04 22:15:39 +0000187
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000188 if (current != NULL) {
189 fileptr->next = current;
190 current->prev = fileptr;
191 renumber(current);
192 current_x = 0;
193 placewewant = 0;
194 } else if (fileptr->next == NULL) {
195 filebot = fileptr;
Robert Siemborski63b3d7e2000-07-04 22:15:39 +0000196 new_magicline();
197
198 /* Update the edit buffer */
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000199 load_file();
200 }
Adam Rogoyski1e328fb2000-07-08 03:57:16 +0000201 statusbar(_("Read %d lines"), num_lines);
202 totlines += num_lines;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000203
204 free(buf);
205 close(fd);
206
207 return 1;
208}
209
210/* Open the file (and decide if it exists) */
211int open_file(char *filename, int insert, int quiet)
212{
213 int fd;
214 struct stat fileinfo;
215
216 if (!strcmp(filename, "") || stat(filename, &fileinfo) == -1) {
217 if (insert) {
218 if (!quiet)
219 statusbar(_("\"%s\" not found"), filename);
220 return -1;
221 } else {
222 /* We have a new file */
223 statusbar(_("New File"));
224 new_file();
225 }
226 } else if ((fd = open(filename, O_RDONLY)) == -1) {
227 if (!quiet)
228 statusbar("%s: %s", strerror(errno), filename);
229 return -1;
230 } else { /* File is A-OK */
231 if (S_ISDIR(fileinfo.st_mode)) {
232 statusbar(_("File \"%s\" is a directory"), filename);
Chris Allegrettaf45c18d2000-09-16 05:25:06 +0000233 if (!insert)
234 new_file();
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000235 return -1;
236 }
237 if (!quiet)
238 statusbar(_("Reading File"));
239 read_file(fd, filename);
240 }
241
242 return 1;
243}
244
245int do_insertfile(void)
246{
247 int i;
Chris Allegretta25f4e582000-11-25 05:03:20 +0000248 char *realname = NULL;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000249
250 wrap_reset();
Chris Allegretta7da4e9f2000-11-06 02:57:22 +0000251 i = statusq(1, writefile_list, WRITEFILE_LIST_LEN, "",
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000252 _("File to insert [from ./] "));
253 if (i != -1) {
254
255#ifdef DEBUG
256 fprintf(stderr, "filename is %s", answer);
257#endif
258
Chris Allegretta25f4e582000-11-25 05:03:20 +0000259 realname = real_dir_from_tilde(answer);
260
261 i = open_file(realname, 1, 0);
262 free(realname);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000263
264 dump_buffer(fileage);
265 set_modified();
266
267 /* Here we want to rebuild the edit window */
Robert Siemborskidd53ec22000-07-04 02:35:19 +0000268 fix_editbot();
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000269
270 /* If we've gone off the bottom, recenter, otherwise just redraw */
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000271 if (current->lineno > editbot->lineno)
Chris Allegretta234a34d2000-07-29 04:33:38 +0000272 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000273 else
274 edit_refresh();
275
276 UNSET(KEEP_CUTBUFFER);
277 display_main_list();
278 return i;
279 } else {
280 statusbar(_("Cancelled"));
281 UNSET(KEEP_CUTBUFFER);
282 display_main_list();
283 return 0;
284 }
285}
286
287/*
288 * Write a file out. If tmp is nonzero, we set the umask to 0600,
289 * we don't set the global variable filename to it's name, and don't
290 * print out how many lines we wrote on the statusbar.
291 *
292 * Note that tmp is only set to 1 for storing temporary files internal
Chris Allegretta30885552000-07-14 01:20:12 +0000293 * to the editor, and is completely different from TEMP_OPT.
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000294 */
295int write_file(char *name, int tmp)
296{
297 long size, lineswritten = 0;
298 char buf[PATH_MAX + 1];
299 filestruct *fileptr;
300 int fd, mask = 0;
301 struct stat st;
Chris Allegretta0f5dfef2000-11-24 14:02:57 +0000302 static char *realname = NULL;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000303
304 if (!strcmp(name, "")) {
305 statusbar(_("Cancelled"));
306 return -1;
307 }
308 titlebar();
309 fileptr = fileage;
Chris Allegretta0f5dfef2000-11-24 14:02:57 +0000310
311 if (realname != NULL)
312 free(realname);
313
Chris Allegrettabe77c612000-11-24 14:00:16 +0000314#ifndef DISABLE_TABCOMP
315 realname = real_dir_from_tilde(name);
316#else
317 realname = mallocstrcpy(realname, name);
318#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000319
Chris Allegrettaacb62342000-07-21 22:42:46 +0000320 /* Check to see if the file is a regular file and FOLLOW_SYMLINKS is
321 set. If so then don't do the delete and recreate code which would
322 cause unexpected behavior */
Chris Allegrettabe77c612000-11-24 14:00:16 +0000323 lstat(realname, &st);
Chris Allegrettaacb62342000-07-21 22:42:46 +0000324
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000325 /* Open the file and truncate it. Trust the symlink. */
Chris Allegrettaacb62342000-07-21 22:42:46 +0000326 if ((ISSET(FOLLOW_SYMLINKS) || !S_ISLNK(st.st_mode)) && !tmp) {
Chris Allegretta962c3c92000-07-24 21:52:17 +0000327
Chris Allegrettabe77c612000-11-24 14:00:16 +0000328 if ((fd = open(realname, O_WRONLY | O_CREAT | O_TRUNC,
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000329 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH |
330 S_IWOTH)) == -1) {
Chris Allegrettaa299b032000-07-14 02:44:02 +0000331 if (ISSET(TEMP_OPT)) {
332 UNSET(TEMP_OPT);
333 return do_writeout(1);
334 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000335 statusbar(_("Could not open file for writing: %s"),
336 strerror(errno));
Chris Allegrettabe77c612000-11-24 14:00:16 +0000337 free(realname);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000338 return -1;
339 }
340 }
341 /* Don't follow symlink. Create new file. */
342 else {
Chris Allegrettabe77c612000-11-24 14:00:16 +0000343 if (strlen(realname) > (PATH_MAX - 7)) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000344 statusbar(_("Could not open file: Path length exceeded."));
345 return -1;
346 }
347
348 memset(buf, 0x00, PATH_MAX + 1);
Chris Allegrettabe77c612000-11-24 14:00:16 +0000349 strcat(buf, realname);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000350 strcat(buf, ".XXXXXX");
351 if ((fd = mkstemp(buf)) == -1) {
Chris Allegrettaa299b032000-07-14 02:44:02 +0000352 if (ISSET(TEMP_OPT)) {
353 UNSET(TEMP_OPT);
354 return do_writeout(1);
Chris Allegrettabd9e7c32000-10-26 01:44:42 +0000355 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000356 statusbar(_("Could not open file for writing: %s"),
357 strerror(errno));
358 return -1;
359 }
360 }
361
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000362 dump_buffer(fileage);
363 while (fileptr != NULL && fileptr->next != NULL) {
Robert Siemborski63b3d7e2000-07-04 22:15:39 +0000364 /* Next line is so we discount the "magic line" */
Chris Allegrettabd9e7c32000-10-26 01:44:42 +0000365 if (filebot == fileptr && fileptr->data[0] == '\0')
366 break;
Robert Siemborski63b3d7e2000-07-04 22:15:39 +0000367
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000368 size = write(fd, fileptr->data, strlen(fileptr->data));
369 if (size == -1) {
370 statusbar(_("Could not open file for writing: %s"),
371 strerror(errno));
372 return -1;
373 } else {
374#ifdef DEBUG
375 fprintf(stderr, _("Wrote >%s\n"), fileptr->data);
376#endif
377 }
378 write(fd, "\n", 1);
379
380 fileptr = fileptr->next;
381 lineswritten++;
382 }
383
384 if (fileptr != NULL) {
385 size = write(fd, fileptr->data, strlen(fileptr->data));
386 if (size == -1) {
387 statusbar(_("Could not open file for writing: %s"),
388 strerror(errno));
389 return -1;
390 } else if (size > 0) {
391 size = write(fd, "\n", 1);
392 if (size == -1) {
393 statusbar(_("Could not open file for writing: %s"),
394 strerror(errno));
395 return -1;
396 }
397 }
398 }
399
400
401 if (close(fd) == -1) {
Chris Allegrettabe77c612000-11-24 14:00:16 +0000402 statusbar(_("Could not close %s: %s"), realname, strerror(errno));
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000403 unlink(buf);
404 return -1;
405 }
406
407 if (!ISSET(FOLLOW_SYMLINKS) || tmp) {
Chris Allegrettabe77c612000-11-24 14:00:16 +0000408 if (stat(realname, &st) == -1) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000409 /* Use default umask as file permisions if file is a new file. */
410 mask = umask(0);
411 umask(mask);
412
413 if (tmp) /* We don't want anyone reading our temporary file! */
414 mask = 0600;
415 else
416 mask = 0666 & ~mask;
417
418 } else {
419 /* Use permissions from file we are overwriting. */
420 mask = st.st_mode;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000421 if (unlink(realname) == -1) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000422 if (errno != ENOENT) {
423 statusbar(_("Could not open %s for writing: %s"),
Chris Allegrettabe77c612000-11-24 14:00:16 +0000424 realname, strerror(errno));
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000425 unlink(buf);
426 return -1;
427 }
428 }
429 }
430
Chris Allegrettabe77c612000-11-24 14:00:16 +0000431 if (link(buf, realname) != -1)
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000432 unlink(buf);
433 else if (errno != EPERM) {
434 statusbar(_("Could not open %s for writing: %s"),
435 name, strerror(errno));
436 unlink(buf);
437 return -1;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000438 } else if (rename(buf, realname) == -1) { /* Try a rename?? */
439 statusbar(_("Could not open %s for writing: %s"),
440 realname, strerror(errno));
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000441 unlink(buf);
442 return -1;
443 }
Chris Allegrettabe77c612000-11-24 14:00:16 +0000444 if (chmod(realname, mask) == -1) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000445 statusbar(_("Could not set permissions %o on %s: %s"),
Chris Allegrettabe77c612000-11-24 14:00:16 +0000446 mask, realname, strerror(errno));
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000447 }
448
449 }
450 if (!tmp) {
Chris Allegrettabe77c612000-11-24 14:00:16 +0000451 strncpy(filename, realname, 132);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000452 statusbar(_("Wrote %d lines"), lineswritten);
Chris Allegretta650e8a42000-11-24 14:15:17 +0000453 UNSET(MODIFIED);
454 titlebar();
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000455 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000456 return 1;
457}
458
459int do_writeout(int exiting)
460{
461 int i = 0;
Chris Allegretta8a0de3b2000-11-24 20:45:14 +0000462#ifdef NANO_EXTRA
463 static int did_cred = 0;
464#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000465
Chris Allegretta92d2bab2000-11-02 14:53:46 +0000466 answer = mallocstrcpy(answer, filename);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000467
Chris Allegretta962c3c92000-07-24 21:52:17 +0000468 if ((exiting) && (ISSET(TEMP_OPT))) {
Chris Allegrettabd9e7c32000-10-26 01:44:42 +0000469 if (filename[0]) {
Chris Allegretta962c3c92000-07-24 21:52:17 +0000470 i = write_file(answer, 0);
471 display_main_list();
472 return i;
Chris Allegrettabd9e7c32000-10-26 01:44:42 +0000473 } else {
Chris Allegretta461b2a92000-07-24 22:05:18 +0000474 UNSET(TEMP_OPT);
475 do_exit();
476
477 /* They cancelled, abort quit */
478 return -1;
Chris Allegretta962c3c92000-07-24 21:52:17 +0000479 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000480 }
481
482 while (1) {
Chris Allegretta7da4e9f2000-11-06 02:57:22 +0000483 i = statusq(1, writefile_list, WRITEFILE_LIST_LEN, answer,
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000484 _("File Name to write"));
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000485
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000486 if (i != -1) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000487
488#ifdef DEBUG
489 fprintf(stderr, _("filename is %s"), answer);
490#endif
Chris Allegretta8a0de3b2000-11-24 20:45:14 +0000491
492#ifdef NANO_EXTRA
493 if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy") && !did_cred) {
494 do_credits();
495 did_cred = 1;
496 return - 1;
497 }
498#endif
Chris Allegretta92d2bab2000-11-02 14:53:46 +0000499 if (strcmp(answer, filename)) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000500 struct stat st;
501 if (!stat(answer, &st)) {
502 i = do_yesno(0, 0, _("File exists, OVERWRITE ?"));
503
504 if (!i || (i == -1))
505 continue;
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000506 }
507 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000508 i = write_file(answer, 0);
509
510 display_main_list();
511 return i;
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000512 } else {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000513 statusbar(_("Cancelled"));
514 display_main_list();
515 return 0;
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000516 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000517 }
518}
519
520int do_writeout_void(void)
521{
522 return do_writeout(0);
523}
Chris Allegretta04d848e2000-11-05 17:54:41 +0000524
Chris Allegrettabe77c612000-11-24 14:00:16 +0000525#ifndef DISABLE_TABCOMP
526static char **homedirs;
527
528/* Return a malloc()ed string containing the actual directory, used
529 * to convert ~user and ~/ notation...
530 */
531char *real_dir_from_tilde (char *buf)
532{
Chris Allegretta04fec912000-11-25 04:43:43 +0000533 char *dirtmp = NULL, *line = NULL, byte[1], *lineptr;
534 int fd, i, status, searchctr = 1;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000535
536 if (buf[0] == '~') {
Chris Allegretta27731842000-11-28 05:53:41 +0000537 if (buf[1] == '~')
538 goto abort; /* Handle ~~ without segfaulting =) */
539 else if (buf[1] == '/') {
Chris Allegrettabe77c612000-11-24 14:00:16 +0000540 if (getenv("HOME") != NULL) {
541 dirtmp = nmalloc(strlen(buf) + 2 + strlen(getenv("HOME")));
542
543 sprintf(dirtmp, "%s/%s", getenv("HOME"), &buf[2]);
544 }
545 }
546 else if (buf[1] != 0) {
Chris Allegrettabe77c612000-11-24 14:00:16 +0000547
Chris Allegretta04fec912000-11-25 04:43:43 +0000548 if((fd = open("/etc/passwd", O_RDONLY)) == -1)
549 goto abort;
550
551 /* Figure how how much of of the str we need to compare */
552 for (searchctr = 1; buf[searchctr] != '/' &&
553 buf[searchctr] != 0; searchctr++)
554 ;
555
556 do {
557 i = 0;
558 line = nmalloc(1);
559 while ((status = read(fd, byte, 1)) != 0 && byte[0] != '\n') {
560
561 line[i] = byte[0];
562 i++;
563 line = nrealloc(line, i+1);
564 }
565 line[i] = 0;
566
567 if (i == 0)
568 goto abort;
569
570 line[i] = 0;
571 lineptr = strtok(line, ":");
572
573 if (!strncmp(lineptr, &buf[1], searchctr - 1)) {
574
575 /* Okay, skip to the password portion now */
576 for (i = 0; i <= 4 && lineptr != NULL; i++)
577 lineptr = strtok(NULL, ":");
578
579 if (lineptr == NULL)
580 goto abort;
581
582 /* Else copy the new string into the new buf */
583 dirtmp = nmalloc(strlen(buf) + 2 + strlen(lineptr));
584
585 sprintf(dirtmp, "%s%s", lineptr, &buf[searchctr]);
586 free(line);
587 break;
588 }
589
590 free(line);
591
592 } while (status != 0);
Chris Allegrettabe77c612000-11-24 14:00:16 +0000593 }
594 }
595 else
596 dirtmp = mallocstrcpy(dirtmp, buf);
597
598 return dirtmp;
Chris Allegretta04fec912000-11-25 04:43:43 +0000599
600abort:
601 dirtmp = mallocstrcpy(dirtmp, buf);
602 return dirtmp;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000603}
604
605/* Tack a slash onto the string we're completing if it's a directory */
Chris Allegretta04fec912000-11-25 04:43:43 +0000606int append_slash_if_dir(char *buf, int *lastWasTab, int *place)
Chris Allegrettabe77c612000-11-24 14:00:16 +0000607{
608 char *dirptr;
609 struct stat fileinfo;
Chris Allegretta04fec912000-11-25 04:43:43 +0000610 int ret = 0;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000611
612 dirptr = real_dir_from_tilde(buf);
613
614 if (stat(dirptr, &fileinfo) == -1)
Chris Allegretta04fec912000-11-25 04:43:43 +0000615 ret = 0;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000616 else if (S_ISDIR(fileinfo.st_mode)) {
617 strncat(buf, "/", 1);
618 *place += 1;
619 /* now we start over again with # of tabs so far */
620 *lastWasTab = 0;
Chris Allegretta04fec912000-11-25 04:43:43 +0000621 ret = 1;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000622 }
623
624 if (dirptr != buf)
625 free(dirptr);
Chris Allegretta04fec912000-11-25 04:43:43 +0000626
627 return ret;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000628}
Chris Allegretta04d848e2000-11-05 17:54:41 +0000629
630/*
631 * These functions (username_tab_completion, cwd_tab_completion, and
632 * input_tab were taken from busybox 0.46 (cmdedit.c). Here is the notice
633 * from that file:
634 *
635 * Termios command line History and Editting, originally
636 * intended for NetBSD sh (ash)
637 * Copyright (c) 1999
638 * Main code: Adam Rogoyski <rogoyski@cs.utexas.edu>
639 * Etc: Dave Cinege <dcinege@psychosis.com>
640 * Majorly adjusted/re-written for busybox:
641 * Erik Andersen <andersee@debian.org>
642 *
643 * You may use this code as you wish, so long as the original author(s)
644 * are attributed in any redistributions of the source code.
645 * This code is 'as is' with no warranty.
646 * This code may safely be consumed by a BSD or GPL license.
647 */
648
649char **username_tab_completion(char *buf, int *num_matches)
650{
Chris Allegrettabe77c612000-11-24 14:00:16 +0000651 char **matches = (char **) NULL, *line = NULL, *lineptr;
652 char *matchline = NULL, *matchdir = NULL;
653
654 int fd, i = 0, status = 1;
655 char byte[1];
656
657 if((fd = open("/etc/passwd", O_RDONLY)) == -1) {
658 return NULL;
659 }
660
661 if (homedirs != NULL) {
662 for (i = 0; i < *num_matches; i++)
663 free(homedirs[i]);
664 free(homedirs);
665 homedirs = (char **) NULL;
666 *num_matches = 0;
667 }
668 matches = nmalloc(BUFSIZ);
669 homedirs = nmalloc(BUFSIZ);
670 strcat(buf, "*");
671 do {
672 i = 0;
673 line = nmalloc(1);
674 while ((status = read(fd, byte, 1)) != 0 && byte[0] != '\n') {
675
676 line[i] = byte[0];
677 i++;
678 line = nrealloc(line, i+1);
679 }
680
681 if (i == 0)
682 break;
683
684 line[i] = 0;
685 lineptr = strtok(line, ":");
686
687 if (check_wildcard_match(line, &buf[1]) == TRUE) {
688
689 if (*num_matches == BUFSIZ)
690 break;
691
692 /* Cool, found a match. Add it to the list
693 * This makes a lot more sense to me (Chris) this way...
694 */
695 matchline = nmalloc(strlen(line) + 2);
696 sprintf(matchline, "~%s", line);
697
698 for (i = 0; i <= 4 && lineptr != NULL; i++)
699 lineptr = strtok(NULL, ":");
700
701 if (lineptr == NULL)
702 break;
703
704 matchdir = mallocstrcpy(matchdir, lineptr);
705 homedirs[*num_matches] = matchdir;
706 matches[*num_matches] = matchline;
707
708 ++*num_matches;
709
710 /* If there's no more room, bail out */
711 if (*num_matches == BUFSIZ)
712 break;
713 }
714
715 free(line);
716
717 } while (status != 0);
718
719 close(fd);
720 return matches;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000721#ifdef DEBUG
722 fprintf(stderr, "\nin username_tab_completion\n");
723#endif
724 return (matches);
725}
726
727/* This was originally called exe_n_cwd_tab_completion, but we're not
728 worried about executables, only filenames :> */
729
730char **cwd_tab_completion(char *buf, int *num_matches)
731{
Chris Allegrettabe77c612000-11-24 14:00:16 +0000732 char *dirName, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000733 char **matches = (char **) NULL;
734 DIR *dir;
735 struct dirent *next;
736
Chris Allegretta2c975222000-11-15 01:25:42 +0000737 matches = nmalloc(BUFSIZ);
Chris Allegretta04d848e2000-11-05 17:54:41 +0000738
739 /* Stick a wildcard onto the buf, for later use */
740 strcat(buf, "*");
741
Chris Allegretta2c975222000-11-15 01:25:42 +0000742 /* Okie, if there's a / in the buffer, strip out the directory part */
Chris Allegretta04d848e2000-11-05 17:54:41 +0000743 if (strcmp(buf, "") && strstr(buf, "/")) {
744 dirName = malloc(strlen(buf) + 1);
745 tmp = buf + strlen(buf);
746 while (*tmp != '/' && tmp != buf)
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000747 tmp--;
Chris Allegretta442f2c52000-11-14 17:46:06 +0000748
Chris Allegretta04d848e2000-11-05 17:54:41 +0000749 tmp++;
750
Chris Allegretta442f2c52000-11-14 17:46:06 +0000751 strncpy(dirName, buf, tmp - buf + 1);
752 dirName[tmp - buf] = 0;
Chris Allegretta442f2c52000-11-14 17:46:06 +0000753
Chris Allegretta04d848e2000-11-05 17:54:41 +0000754 } else {
755 if ((dirName = getcwd(NULL, 0)) == NULL)
756 return matches;
757 else
758 tmp = buf;
759 }
760
761#ifdef DEBUG
762 fprintf(stderr, "\nDir = %s\n", dirName);
763 fprintf(stderr, "\nbuf = %s\n", buf);
764 fprintf(stderr, "\ntmp = %s\n", tmp);
765#endif
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000766
Chris Allegrettabe77c612000-11-24 14:00:16 +0000767 dirtmp = real_dir_from_tilde(dirName);
768 free(dirName);
769 dirName = dirtmp;
770
771#ifdef DEBUG
772 fprintf(stderr, "\nDir = %s\n", dirName);
773 fprintf(stderr, "\nbuf = %s\n", buf);
774 fprintf(stderr, "\ntmp = %s\n", tmp);
775#endif
776
777
Chris Allegretta04d848e2000-11-05 17:54:41 +0000778 dir = opendir(dirName);
779 if (!dir) {
780 /* Don't print an error, just shut up and return */
781 *num_matches = 0;
782 beep();
783 return (matches);
784 }
785 while ((next = readdir(dir)) != NULL) {
786
787 /* Some quick sanity checks */
788 if ((strcmp(next->d_name, "..") == 0)
789 || (strcmp(next->d_name, ".") == 0)) {
790 continue;
791 }
792#ifdef DEBUG
Chris Allegretta2c975222000-11-15 01:25:42 +0000793 fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta04d848e2000-11-05 17:54:41 +0000794#endif
795 /* See if this matches */
796 if (check_wildcard_match(next->d_name, tmp) == TRUE) {
Chris Allegretta7d97ce72000-11-06 04:04:15 +0000797
798 /* Cool, found a match. Add it to the list
799 * This makes a lot more sense to me (Chris) this way...
800 */
801 tmp2 = NULL;
802 tmp2 = nmalloc(strlen(next->d_name) + 1);
803 strcpy(tmp2, next->d_name);
804 matches[*num_matches] = tmp2;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000805 ++*num_matches;
Chris Allegretta2c975222000-11-15 01:25:42 +0000806
807 /* If there's no more room, bail out */
808 if (*num_matches == BUFSIZ)
809 break;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000810 }
811 }
812
813 return (matches);
814}
815
Chris Allegretta442f2c52000-11-14 17:46:06 +0000816/* This function now has an arg which refers to how much the
Chris Allegretta04d848e2000-11-05 17:54:41 +0000817 * statusbar (place) should be advanced, i.e. the new cursor pos.
818 */
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000819char *input_tab(char *buf, int place, int *lastWasTab, int *newplace)
Chris Allegretta04d848e2000-11-05 17:54:41 +0000820{
821 /* Do TAB completion */
Chris Allegrettaec58a992000-11-05 21:54:23 +0000822 static int num_matches = 0, match_matches = 0;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000823 static char **matches = (char **) NULL;
Chris Allegretta442f2c52000-11-14 17:46:06 +0000824 int pos = place, i = 0, col = 0, editline = 0;
Chris Allegretta04fec912000-11-25 04:43:43 +0000825 int longestname = 0, is_dir = 0;
Chris Allegretta3b0d1442000-11-05 21:56:54 +0000826 char *foo;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000827
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000828 if (*lastWasTab == FALSE) {
Chris Allegretta442f2c52000-11-14 17:46:06 +0000829 char *tmp, *copyto, *matchBuf;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000830
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000831 *lastWasTab = 1;
832
Chris Allegretta04d848e2000-11-05 17:54:41 +0000833 /* Make a local copy of the string -- up to the position of the
834 cursor */
Chris Allegrettae118acc2000-11-06 05:40:03 +0000835 matchBuf = (char *) calloc(strlen(buf) + 2, sizeof(char));
836
Chris Allegretta04d848e2000-11-05 17:54:41 +0000837 strncpy(matchBuf, buf, place);
838 tmp = matchBuf;
839
840 /* skip any leading white space */
Chris Allegretta63a89d32000-11-18 03:05:50 +0000841 while (*tmp && isspace((int) *tmp))
Chris Allegretta04d848e2000-11-05 17:54:41 +0000842 ++tmp;
843
844 /* Free up any memory already allocated */
845 if (matches != NULL) {
Chris Allegretta442f2c52000-11-14 17:46:06 +0000846 for (i = i; i < num_matches; i++)
847 free(matches[i]);
Chris Allegretta04d848e2000-11-05 17:54:41 +0000848 free(matches);
849 matches = (char **) NULL;
Chris Allegretta7d97ce72000-11-06 04:04:15 +0000850 num_matches = 0;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000851 }
852
853 /* If the word starts with `~' and there is no slash in the word,
854 * then try completing this word as a username. */
855
Chris Allegrettabe77c612000-11-24 14:00:16 +0000856 /* FIXME -- this check is broken! */
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000857 if (*tmp == '~' && !strchr(tmp, '/'))
Chris Allegrettabe77c612000-11-24 14:00:16 +0000858 matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta04d848e2000-11-05 17:54:41 +0000859
860 /* Try to match everything in the current working directory that
861 * matches. */
862 if (!matches)
863 matches = cwd_tab_completion(tmp, &num_matches);
864
865 /* Don't leak memory */
866 free(matchBuf);
867
Chris Allegretta442f2c52000-11-14 17:46:06 +0000868#ifdef DEBUG
869 fprintf(stderr, "%d matches found...\n", num_matches);
870#endif
Chris Allegretta04d848e2000-11-05 17:54:41 +0000871 /* Did we find exactly one match? */
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000872 switch (num_matches) {
Chris Allegrettae118acc2000-11-06 05:40:03 +0000873 case 0:
Chris Allegretta24342432000-11-06 05:41:20 +0000874 blank_edit();
Chris Allegretta2c975222000-11-15 01:25:42 +0000875 wrefresh(edit);
Chris Allegrettae118acc2000-11-06 05:40:03 +0000876 break;
877 case 1:
Chris Allegretta442f2c52000-11-14 17:46:06 +0000878
879 buf = nrealloc(buf, strlen(buf) + strlen(matches[0]) + 1);
880
881 if (strcmp(buf, "") && strstr(buf, "/")) {
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000882 for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
883 tmp--);
Chris Allegretta442f2c52000-11-14 17:46:06 +0000884 tmp++;
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000885 } else
Chris Allegretta442f2c52000-11-14 17:46:06 +0000886 tmp = buf;
887
Chris Allegretta04fec912000-11-25 04:43:43 +0000888 if (!strcmp(tmp, matches[0]))
889 is_dir = append_slash_if_dir(buf, lastWasTab, newplace);
890
891 if (is_dir)
892 break;
Chris Allegretta442f2c52000-11-14 17:46:06 +0000893
894 copyto = tmp;
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000895 for (pos = 0; *tmp == matches[0][pos] &&
896 pos <= strlen(matches[0]); pos++)
Chris Allegretta442f2c52000-11-14 17:46:06 +0000897 tmp++;
898
Chris Allegretta2c975222000-11-15 01:25:42 +0000899 /* write out the matched name */
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000900 strncpy(copyto, matches[0], strlen(matches[0]) + 1);
Chris Allegretta442f2c52000-11-14 17:46:06 +0000901 *newplace += strlen(matches[0]) - pos;
902
Chris Allegrettabe77c612000-11-24 14:00:16 +0000903 /* Is it a directory? */
904 append_slash_if_dir(buf, lastWasTab, newplace);
905
Chris Allegrettae118acc2000-11-06 05:40:03 +0000906 break;
907 default:
Chris Allegrettaec58a992000-11-05 21:54:23 +0000908 /* Check to see if all matches share a beginning, and if so
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000909 tack it onto buf and then beep */
Chris Allegrettaec58a992000-11-05 21:54:23 +0000910
Chris Allegretta442f2c52000-11-14 17:46:06 +0000911 if (strcmp(buf, "") && strstr(buf, "/")) {
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000912 for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
913 tmp--);
Chris Allegretta442f2c52000-11-14 17:46:06 +0000914 tmp++;
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000915 } else
Chris Allegretta442f2c52000-11-14 17:46:06 +0000916 tmp = buf;
917
918 for (pos = 0; *tmp == matches[0][pos] && *tmp != 0 &&
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000919 pos <= strlen(matches[0]); pos++)
Chris Allegretta442f2c52000-11-14 17:46:06 +0000920 tmp++;
921
Chris Allegrettaec58a992000-11-05 21:54:23 +0000922 while (1) {
923 match_matches = 0;
924
925 for (i = 0; i < num_matches; i++) {
926 if (matches[i][pos] == 0)
927 break;
928 else if (matches[i][pos] == matches[0][pos])
929 match_matches++;
930 }
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000931 if (match_matches == num_matches &&
932 (i == num_matches || matches[i] != 0)) {
Chris Allegrettaec58a992000-11-05 21:54:23 +0000933 /* All the matches have the same character at pos+1,
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000934 so paste it into buf... */
Chris Allegretta75864952000-11-05 22:48:35 +0000935 buf = nrealloc(buf, strlen(buf) + 2);
Chris Allegretta442f2c52000-11-14 17:46:06 +0000936 strncat(buf, matches[0] + pos, 1);
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000937 *newplace += 1;
Chris Allegrettaec58a992000-11-05 21:54:23 +0000938 pos++;
Chris Allegretta75864952000-11-05 22:48:35 +0000939 } else {
Chris Allegrettaec58a992000-11-05 21:54:23 +0000940 beep();
941 break;
942 }
943 }
Chris Allegrettae118acc2000-11-06 05:40:03 +0000944 break;
Chris Allegrettaec58a992000-11-05 21:54:23 +0000945 }
Chris Allegretta04d848e2000-11-05 17:54:41 +0000946 } else {
947 /* Ok -- the last char was a TAB. Since they
948 * just hit TAB again, print a list of all the
949 * available choices... */
950 if (matches && num_matches > 0) {
Chris Allegretta04d848e2000-11-05 17:54:41 +0000951
952 /* Blank the edit window, and print the matches out there */
953 blank_edit();
954 wmove(edit, 0, 0);
955
Chris Allegrettaec58a992000-11-05 21:54:23 +0000956 editline = 0;
Chris Allegretta2c975222000-11-15 01:25:42 +0000957
Chris Allegrettaec58a992000-11-05 21:54:23 +0000958 /* Figure out the length of the longest filename */
959 for (i = 0; i < num_matches; i++)
960 if (strlen(matches[i]) > longestname)
961 longestname = strlen(matches[i]);
962
963 if (longestname > COLS - 1)
964 longestname = COLS - 1;
965
Chris Allegretta3b0d1442000-11-05 21:56:54 +0000966 foo = nmalloc(longestname + 5);
967
Chris Allegretta04d848e2000-11-05 17:54:41 +0000968 /* Print the list of matches */
969 for (i = 0, col = 0; i < num_matches; i++) {
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000970
Chris Allegrettaec58a992000-11-05 21:54:23 +0000971 /* make each filename shown be the same length as the longest
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000972 filename, with two spaces at the end */
Chris Allegrettaec58a992000-11-05 21:54:23 +0000973 snprintf(foo, longestname + 1, matches[i]);
974 while (strlen(foo) < longestname)
975 strcat(foo, " ");
976
977 strcat(foo, " ");
978
Chris Allegretta442f2c52000-11-14 17:46:06 +0000979 /* Disable el cursor */
980 curs_set(0);
Chris Allegretta75864952000-11-05 22:48:35 +0000981 /* now, put the match on the screen */
982 waddnstr(edit, foo, strlen(foo));
983 col += strlen(foo);
984
985 /* And if the next match isn't going to fit on the
986 line, move to the next one */
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000987 if (col > (COLS - longestname) && matches[i + 1] != NULL) {
Chris Allegrettaec58a992000-11-05 21:54:23 +0000988 editline++;
989 wmove(edit, editline, 0);
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000990 if (editline == editwinrows - 1) {
991 waddstr(edit, _("(more)"));
992 break;
993 }
Chris Allegretta04d848e2000-11-05 17:54:41 +0000994 col = 0;
995 }
996 }
Chris Allegretta3b0d1442000-11-05 21:56:54 +0000997 free(foo);
Chris Allegretta04d848e2000-11-05 17:54:41 +0000998 wrefresh(edit);
Chris Allegretta24dd8d62000-11-06 05:45:48 +0000999 } else
1000 beep();
Chris Allegretta04d848e2000-11-05 17:54:41 +00001001
1002 }
1003
1004 edit_refresh();
Chris Allegretta442f2c52000-11-14 17:46:06 +00001005 curs_set(1);
1006 return buf;
Chris Allegretta04d848e2000-11-05 17:54:41 +00001007}
Chris Allegrettabe77c612000-11-24 14:00:16 +00001008#endif