blob: dc1220a26d861a1a5bda8fd9b498d91760507f57 [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 Allegretta09a80842000-11-30 02:31:13 +0000259#ifndef DISABLE_TABCOMP
Chris Allegretta25f4e582000-11-25 05:03:20 +0000260 realname = real_dir_from_tilde(answer);
Chris Allegretta09a80842000-11-30 02:31:13 +0000261#else
262 realname = mallocstrcpy(realname, answer);
263#endif
Chris Allegretta25f4e582000-11-25 05:03:20 +0000264
265 i = open_file(realname, 1, 0);
266 free(realname);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000267
268 dump_buffer(fileage);
269 set_modified();
270
271 /* Here we want to rebuild the edit window */
Robert Siemborskidd53ec22000-07-04 02:35:19 +0000272 fix_editbot();
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000273
274 /* If we've gone off the bottom, recenter, otherwise just redraw */
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000275 if (current->lineno > editbot->lineno)
Chris Allegretta234a34d2000-07-29 04:33:38 +0000276 edit_update(current, CENTER);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000277 else
278 edit_refresh();
279
280 UNSET(KEEP_CUTBUFFER);
281 display_main_list();
282 return i;
283 } else {
284 statusbar(_("Cancelled"));
285 UNSET(KEEP_CUTBUFFER);
286 display_main_list();
287 return 0;
288 }
289}
290
291/*
292 * Write a file out. If tmp is nonzero, we set the umask to 0600,
293 * we don't set the global variable filename to it's name, and don't
294 * print out how many lines we wrote on the statusbar.
295 *
Chris Allegretta3dbb2782000-12-02 04:36:50 +0000296 * tmp means we are writing a tmp file in a secute fashion. We use
297 * it when spell checking or dumping the file on an error.
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000298 */
299int write_file(char *name, int tmp)
300{
301 long size, lineswritten = 0;
302 char buf[PATH_MAX + 1];
303 filestruct *fileptr;
Chris Allegretta07f9ee02000-12-04 05:15:39 +0000304 int fd, mask = 0, realexists, anyexists;
305 struct stat st, st2;
Chris Allegretta0f5dfef2000-11-24 14:02:57 +0000306 static char *realname = NULL;
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000307
308 if (!strcmp(name, "")) {
309 statusbar(_("Cancelled"));
310 return -1;
311 }
312 titlebar();
313 fileptr = fileage;
Chris Allegretta0f5dfef2000-11-24 14:02:57 +0000314
315 if (realname != NULL)
316 free(realname);
317
Chris Allegrettabe77c612000-11-24 14:00:16 +0000318#ifndef DISABLE_TABCOMP
319 realname = real_dir_from_tilde(name);
320#else
321 realname = mallocstrcpy(realname, name);
322#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000323
Chris Allegrettaf7ee9e62000-12-02 21:13:50 +0000324 /* Save the state of file at the end of the symlink */
325 realexists = stat(realname, &st);
326
Chris Allegrettaacb62342000-07-21 22:42:46 +0000327 /* Check to see if the file is a regular file and FOLLOW_SYMLINKS is
328 set. If so then don't do the delete and recreate code which would
329 cause unexpected behavior */
Chris Allegretta07f9ee02000-12-04 05:15:39 +0000330 anyexists = lstat(realname, &st);
Chris Allegrettaacb62342000-07-21 22:42:46 +0000331
Chris Allegretta07f9ee02000-12-04 05:15:39 +0000332 /* New case: if the file exists, just give up */
333 if (tmp && anyexists != -1)
Chris Allegretta3dbb2782000-12-02 04:36:50 +0000334 return -1;
Chris Allegrettab5bb24c2000-12-06 00:57:54 +0000335 /* NOTE: If you change this statement, you MUST CHANGE the if
336 statement below (that starts "if ((!ISSET(FOLLOW_SYMLINKS)...")
337 to reflect whether or not to link/unlink/rename the file */
Chris Allegrettaf7ee9e62000-12-02 21:13:50 +0000338 else if (ISSET(FOLLOW_SYMLINKS) || !S_ISLNK(st.st_mode)) {
Chris Allegretta962c3c92000-07-24 21:52:17 +0000339
Chris Allegrettafb2226a2000-12-04 05:25:47 +0000340 /* Use O_EXCL if tmp == 1, I suppose */
341 if (tmp)
342 fd = open(realname, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC,
343 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH |
344 S_IWOTH);
345 else
346 fd = open(realname, O_WRONLY | O_CREAT | O_TRUNC,
Chris Allegretta07f9ee02000-12-04 05:15:39 +0000347 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH |
348 S_IWOTH);
349
350 /* First, just give up if we couldn't even open the file */
Chris Allegretta59828492000-12-04 03:31:39 +0000351 if (fd == -1) {
Chris Allegretta20c131c2000-12-04 04:20:09 +0000352 if (!tmp && ISSET(TEMP_OPT)) {
Chris Allegrettaa299b032000-07-14 02:44:02 +0000353 UNSET(TEMP_OPT);
354 return do_writeout(1);
355 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000356 statusbar(_("Could not open file for writing: %s"),
357 strerror(errno));
Chris Allegrettabe77c612000-11-24 14:00:16 +0000358 free(realname);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000359 return -1;
360 }
Chris Allegretta07f9ee02000-12-04 05:15:39 +0000361
362 /* Now we fstat() the file, to make sure it's the same file still!
363 Thanks to Oliver Friedrichs(?) for this code from securityfocus */
364
365 if (fstat(fd, &st2) != 0) {
366 close(fd);
367 return -1;
368 }
369
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000370 }
371 /* Don't follow symlink. Create new file. */
372 else {
Chris Allegrettabe77c612000-11-24 14:00:16 +0000373 if (strlen(realname) > (PATH_MAX - 7)) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000374 statusbar(_("Could not open file: Path length exceeded."));
375 return -1;
376 }
377
378 memset(buf, 0x00, PATH_MAX + 1);
Chris Allegrettabe77c612000-11-24 14:00:16 +0000379 strcat(buf, realname);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000380 strcat(buf, ".XXXXXX");
381 if ((fd = mkstemp(buf)) == -1) {
Chris Allegrettaa299b032000-07-14 02:44:02 +0000382 if (ISSET(TEMP_OPT)) {
383 UNSET(TEMP_OPT);
384 return do_writeout(1);
Chris Allegrettabd9e7c32000-10-26 01:44:42 +0000385 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000386 statusbar(_("Could not open file for writing: %s"),
387 strerror(errno));
388 return -1;
389 }
390 }
391
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000392 dump_buffer(fileage);
393 while (fileptr != NULL && fileptr->next != NULL) {
Robert Siemborski63b3d7e2000-07-04 22:15:39 +0000394 /* Next line is so we discount the "magic line" */
Chris Allegrettabd9e7c32000-10-26 01:44:42 +0000395 if (filebot == fileptr && fileptr->data[0] == '\0')
396 break;
Robert Siemborski63b3d7e2000-07-04 22:15:39 +0000397
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000398 size = write(fd, fileptr->data, strlen(fileptr->data));
399 if (size == -1) {
400 statusbar(_("Could not open file for writing: %s"),
401 strerror(errno));
402 return -1;
403 } else {
404#ifdef DEBUG
405 fprintf(stderr, _("Wrote >%s\n"), fileptr->data);
406#endif
407 }
408 write(fd, "\n", 1);
409
410 fileptr = fileptr->next;
411 lineswritten++;
412 }
413
414 if (fileptr != NULL) {
415 size = write(fd, fileptr->data, strlen(fileptr->data));
416 if (size == -1) {
417 statusbar(_("Could not open file for writing: %s"),
418 strerror(errno));
419 return -1;
420 } else if (size > 0) {
421 size = write(fd, "\n", 1);
422 if (size == -1) {
423 statusbar(_("Could not open file for writing: %s"),
424 strerror(errno));
425 return -1;
426 }
427 }
428 }
429
430
431 if (close(fd) == -1) {
Chris Allegrettabe77c612000-11-24 14:00:16 +0000432 statusbar(_("Could not close %s: %s"), realname, strerror(errno));
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000433 unlink(buf);
434 return -1;
435 }
436
Chris Allegretta71e46402000-12-06 00:40:26 +0000437 if ((!ISSET(FOLLOW_SYMLINKS) && S_ISLNK(st.st_mode)) || tmp) {
Chris Allegrettaf7ee9e62000-12-02 21:13:50 +0000438 if (realexists == -1) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000439 /* Use default umask as file permisions if file is a new file. */
440 mask = umask(0);
441 umask(mask);
442
443 if (tmp) /* We don't want anyone reading our temporary file! */
444 mask = 0600;
445 else
446 mask = 0666 & ~mask;
447
448 } else {
449 /* Use permissions from file we are overwriting. */
450 mask = st.st_mode;
Chris Allegretta581bc602000-12-03 03:01:12 +0000451 if (unlink(realname) == -1) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000452 if (errno != ENOENT) {
453 statusbar(_("Could not open %s for writing: %s"),
Chris Allegrettabe77c612000-11-24 14:00:16 +0000454 realname, strerror(errno));
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000455 unlink(buf);
456 return -1;
457 }
458 }
459 }
460
Chris Allegrettaf7ee9e62000-12-02 21:13:50 +0000461 if (!tmp) {
462 if (link(buf, realname) != -1)
463 unlink(buf);
464 else if (errno != EPERM) {
465 statusbar(_("Could not open %s for writing: %s"),
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000466 name, strerror(errno));
Chris Allegrettaf7ee9e62000-12-02 21:13:50 +0000467 unlink(buf);
468 return -1;
469 } else if (rename(buf, realname) == -1) { /* Try a rename?? */
470 statusbar(_("Could not open %s for writing: %s"),
Chris Allegrettabe77c612000-11-24 14:00:16 +0000471 realname, strerror(errno));
Chris Allegrettaf7ee9e62000-12-02 21:13:50 +0000472 unlink(buf);
473 return -1;
474 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000475 }
Chris Allegrettaf7ee9e62000-12-02 21:13:50 +0000476 if (chmod(realname, mask) == -1)
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000477 statusbar(_("Could not set permissions %o on %s: %s"),
Chris Allegrettabe77c612000-11-24 14:00:16 +0000478 mask, realname, strerror(errno));
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000479
480 }
481 if (!tmp) {
Chris Allegrettabe77c612000-11-24 14:00:16 +0000482 strncpy(filename, realname, 132);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000483 statusbar(_("Wrote %d lines"), lineswritten);
Chris Allegretta650e8a42000-11-24 14:15:17 +0000484 UNSET(MODIFIED);
485 titlebar();
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000486 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000487 return 1;
488}
489
490int do_writeout(int exiting)
491{
492 int i = 0;
Chris Allegretta8a0de3b2000-11-24 20:45:14 +0000493#ifdef NANO_EXTRA
494 static int did_cred = 0;
495#endif
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000496
Chris Allegretta92d2bab2000-11-02 14:53:46 +0000497 answer = mallocstrcpy(answer, filename);
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000498
Chris Allegretta962c3c92000-07-24 21:52:17 +0000499 if ((exiting) && (ISSET(TEMP_OPT))) {
Chris Allegrettabd9e7c32000-10-26 01:44:42 +0000500 if (filename[0]) {
Chris Allegretta962c3c92000-07-24 21:52:17 +0000501 i = write_file(answer, 0);
502 display_main_list();
503 return i;
Chris Allegrettabd9e7c32000-10-26 01:44:42 +0000504 } else {
Chris Allegretta461b2a92000-07-24 22:05:18 +0000505 UNSET(TEMP_OPT);
506 do_exit();
507
508 /* They cancelled, abort quit */
509 return -1;
Chris Allegretta962c3c92000-07-24 21:52:17 +0000510 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000511 }
512
513 while (1) {
Chris Allegretta7da4e9f2000-11-06 02:57:22 +0000514 i = statusq(1, writefile_list, WRITEFILE_LIST_LEN, answer,
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000515 _("File Name to write"));
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000516
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000517 if (i != -1) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000518
519#ifdef DEBUG
520 fprintf(stderr, _("filename is %s"), answer);
521#endif
Chris Allegretta8a0de3b2000-11-24 20:45:14 +0000522
523#ifdef NANO_EXTRA
524 if (exiting && !ISSET(TEMP_OPT) && !strcasecmp(answer, "zzy") && !did_cred) {
525 do_credits();
526 did_cred = 1;
527 return - 1;
528 }
529#endif
Chris Allegretta92d2bab2000-11-02 14:53:46 +0000530 if (strcmp(answer, filename)) {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000531 struct stat st;
532 if (!stat(answer, &st)) {
533 i = do_yesno(0, 0, _("File exists, OVERWRITE ?"));
534
535 if (!i || (i == -1))
536 continue;
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000537 }
538 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000539 i = write_file(answer, 0);
540
541 display_main_list();
542 return i;
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000543 } else {
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000544 statusbar(_("Cancelled"));
545 display_main_list();
546 return 0;
Chris Allegretta4da1fc62000-06-21 03:00:43 +0000547 }
Chris Allegrettabceb1b22000-06-19 04:22:15 +0000548 }
549}
550
551int do_writeout_void(void)
552{
553 return do_writeout(0);
554}
Chris Allegretta04d848e2000-11-05 17:54:41 +0000555
Chris Allegrettabe77c612000-11-24 14:00:16 +0000556#ifndef DISABLE_TABCOMP
557static char **homedirs;
558
559/* Return a malloc()ed string containing the actual directory, used
560 * to convert ~user and ~/ notation...
561 */
562char *real_dir_from_tilde (char *buf)
563{
Chris Allegretta04fec912000-11-25 04:43:43 +0000564 char *dirtmp = NULL, *line = NULL, byte[1], *lineptr;
565 int fd, i, status, searchctr = 1;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000566
567 if (buf[0] == '~') {
Chris Allegretta27731842000-11-28 05:53:41 +0000568 if (buf[1] == '~')
569 goto abort; /* Handle ~~ without segfaulting =) */
570 else if (buf[1] == '/') {
Chris Allegrettabe77c612000-11-24 14:00:16 +0000571 if (getenv("HOME") != NULL) {
572 dirtmp = nmalloc(strlen(buf) + 2 + strlen(getenv("HOME")));
573
574 sprintf(dirtmp, "%s/%s", getenv("HOME"), &buf[2]);
575 }
576 }
577 else if (buf[1] != 0) {
Chris Allegrettabe77c612000-11-24 14:00:16 +0000578
Chris Allegretta04fec912000-11-25 04:43:43 +0000579 if((fd = open("/etc/passwd", O_RDONLY)) == -1)
580 goto abort;
581
582 /* Figure how how much of of the str we need to compare */
583 for (searchctr = 1; buf[searchctr] != '/' &&
584 buf[searchctr] != 0; searchctr++)
585 ;
586
587 do {
588 i = 0;
589 line = nmalloc(1);
590 while ((status = read(fd, byte, 1)) != 0 && byte[0] != '\n') {
591
592 line[i] = byte[0];
593 i++;
594 line = nrealloc(line, i+1);
595 }
596 line[i] = 0;
597
598 if (i == 0)
599 goto abort;
600
601 line[i] = 0;
602 lineptr = strtok(line, ":");
603
604 if (!strncmp(lineptr, &buf[1], searchctr - 1)) {
605
606 /* Okay, skip to the password portion now */
607 for (i = 0; i <= 4 && lineptr != NULL; i++)
608 lineptr = strtok(NULL, ":");
609
610 if (lineptr == NULL)
611 goto abort;
612
613 /* Else copy the new string into the new buf */
614 dirtmp = nmalloc(strlen(buf) + 2 + strlen(lineptr));
615
616 sprintf(dirtmp, "%s%s", lineptr, &buf[searchctr]);
617 free(line);
618 break;
619 }
620
621 free(line);
622
623 } while (status != 0);
Chris Allegrettabe77c612000-11-24 14:00:16 +0000624 }
625 }
626 else
627 dirtmp = mallocstrcpy(dirtmp, buf);
628
629 return dirtmp;
Chris Allegretta04fec912000-11-25 04:43:43 +0000630
631abort:
632 dirtmp = mallocstrcpy(dirtmp, buf);
633 return dirtmp;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000634}
635
636/* Tack a slash onto the string we're completing if it's a directory */
Chris Allegretta04fec912000-11-25 04:43:43 +0000637int append_slash_if_dir(char *buf, int *lastWasTab, int *place)
Chris Allegrettabe77c612000-11-24 14:00:16 +0000638{
639 char *dirptr;
640 struct stat fileinfo;
Chris Allegretta04fec912000-11-25 04:43:43 +0000641 int ret = 0;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000642
643 dirptr = real_dir_from_tilde(buf);
644
645 if (stat(dirptr, &fileinfo) == -1)
Chris Allegretta04fec912000-11-25 04:43:43 +0000646 ret = 0;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000647 else if (S_ISDIR(fileinfo.st_mode)) {
648 strncat(buf, "/", 1);
649 *place += 1;
650 /* now we start over again with # of tabs so far */
651 *lastWasTab = 0;
Chris Allegretta04fec912000-11-25 04:43:43 +0000652 ret = 1;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000653 }
654
655 if (dirptr != buf)
656 free(dirptr);
Chris Allegretta04fec912000-11-25 04:43:43 +0000657
658 return ret;
Chris Allegrettabe77c612000-11-24 14:00:16 +0000659}
Chris Allegretta04d848e2000-11-05 17:54:41 +0000660
661/*
662 * These functions (username_tab_completion, cwd_tab_completion, and
663 * input_tab were taken from busybox 0.46 (cmdedit.c). Here is the notice
664 * from that file:
665 *
666 * Termios command line History and Editting, originally
667 * intended for NetBSD sh (ash)
668 * Copyright (c) 1999
669 * Main code: Adam Rogoyski <rogoyski@cs.utexas.edu>
670 * Etc: Dave Cinege <dcinege@psychosis.com>
671 * Majorly adjusted/re-written for busybox:
672 * Erik Andersen <andersee@debian.org>
673 *
674 * You may use this code as you wish, so long as the original author(s)
675 * are attributed in any redistributions of the source code.
676 * This code is 'as is' with no warranty.
677 * This code may safely be consumed by a BSD or GPL license.
678 */
679
680char **username_tab_completion(char *buf, int *num_matches)
681{
Chris Allegrettabe77c612000-11-24 14:00:16 +0000682 char **matches = (char **) NULL, *line = NULL, *lineptr;
683 char *matchline = NULL, *matchdir = NULL;
684
685 int fd, i = 0, status = 1;
686 char byte[1];
687
688 if((fd = open("/etc/passwd", O_RDONLY)) == -1) {
689 return NULL;
690 }
691
692 if (homedirs != NULL) {
693 for (i = 0; i < *num_matches; i++)
694 free(homedirs[i]);
695 free(homedirs);
696 homedirs = (char **) NULL;
697 *num_matches = 0;
698 }
699 matches = nmalloc(BUFSIZ);
700 homedirs = nmalloc(BUFSIZ);
701 strcat(buf, "*");
702 do {
703 i = 0;
704 line = nmalloc(1);
705 while ((status = read(fd, byte, 1)) != 0 && byte[0] != '\n') {
706
707 line[i] = byte[0];
708 i++;
709 line = nrealloc(line, i+1);
710 }
711
712 if (i == 0)
713 break;
714
715 line[i] = 0;
716 lineptr = strtok(line, ":");
717
718 if (check_wildcard_match(line, &buf[1]) == TRUE) {
719
720 if (*num_matches == BUFSIZ)
721 break;
722
723 /* Cool, found a match. Add it to the list
724 * This makes a lot more sense to me (Chris) this way...
725 */
726 matchline = nmalloc(strlen(line) + 2);
727 sprintf(matchline, "~%s", line);
728
729 for (i = 0; i <= 4 && lineptr != NULL; i++)
730 lineptr = strtok(NULL, ":");
731
732 if (lineptr == NULL)
733 break;
734
735 matchdir = mallocstrcpy(matchdir, lineptr);
736 homedirs[*num_matches] = matchdir;
737 matches[*num_matches] = matchline;
738
739 ++*num_matches;
740
741 /* If there's no more room, bail out */
742 if (*num_matches == BUFSIZ)
743 break;
744 }
745
746 free(line);
747
748 } while (status != 0);
749
750 close(fd);
751 return matches;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000752#ifdef DEBUG
753 fprintf(stderr, "\nin username_tab_completion\n");
754#endif
755 return (matches);
756}
757
758/* This was originally called exe_n_cwd_tab_completion, but we're not
759 worried about executables, only filenames :> */
760
761char **cwd_tab_completion(char *buf, int *num_matches)
762{
Chris Allegrettabe77c612000-11-24 14:00:16 +0000763 char *dirName, *dirtmp = NULL, *tmp = NULL, *tmp2 = NULL;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000764 char **matches = (char **) NULL;
765 DIR *dir;
766 struct dirent *next;
767
Chris Allegretta2c975222000-11-15 01:25:42 +0000768 matches = nmalloc(BUFSIZ);
Chris Allegretta04d848e2000-11-05 17:54:41 +0000769
770 /* Stick a wildcard onto the buf, for later use */
771 strcat(buf, "*");
772
Chris Allegretta2c975222000-11-15 01:25:42 +0000773 /* Okie, if there's a / in the buffer, strip out the directory part */
Chris Allegretta04d848e2000-11-05 17:54:41 +0000774 if (strcmp(buf, "") && strstr(buf, "/")) {
775 dirName = malloc(strlen(buf) + 1);
776 tmp = buf + strlen(buf);
777 while (*tmp != '/' && tmp != buf)
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000778 tmp--;
Chris Allegretta442f2c52000-11-14 17:46:06 +0000779
Chris Allegretta04d848e2000-11-05 17:54:41 +0000780 tmp++;
781
Chris Allegretta442f2c52000-11-14 17:46:06 +0000782 strncpy(dirName, buf, tmp - buf + 1);
783 dirName[tmp - buf] = 0;
Chris Allegretta442f2c52000-11-14 17:46:06 +0000784
Chris Allegretta04d848e2000-11-05 17:54:41 +0000785 } else {
786 if ((dirName = getcwd(NULL, 0)) == NULL)
787 return matches;
788 else
789 tmp = buf;
790 }
791
792#ifdef DEBUG
793 fprintf(stderr, "\nDir = %s\n", dirName);
794 fprintf(stderr, "\nbuf = %s\n", buf);
795 fprintf(stderr, "\ntmp = %s\n", tmp);
796#endif
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000797
Chris Allegrettabe77c612000-11-24 14:00:16 +0000798 dirtmp = real_dir_from_tilde(dirName);
799 free(dirName);
800 dirName = dirtmp;
801
802#ifdef DEBUG
803 fprintf(stderr, "\nDir = %s\n", dirName);
804 fprintf(stderr, "\nbuf = %s\n", buf);
805 fprintf(stderr, "\ntmp = %s\n", tmp);
806#endif
807
808
Chris Allegretta04d848e2000-11-05 17:54:41 +0000809 dir = opendir(dirName);
810 if (!dir) {
811 /* Don't print an error, just shut up and return */
812 *num_matches = 0;
813 beep();
814 return (matches);
815 }
816 while ((next = readdir(dir)) != NULL) {
817
818 /* Some quick sanity checks */
819 if ((strcmp(next->d_name, "..") == 0)
820 || (strcmp(next->d_name, ".") == 0)) {
821 continue;
822 }
823#ifdef DEBUG
Chris Allegretta2c975222000-11-15 01:25:42 +0000824 fprintf(stderr, "Comparing \'%s\'\n", next->d_name);
Chris Allegretta04d848e2000-11-05 17:54:41 +0000825#endif
826 /* See if this matches */
827 if (check_wildcard_match(next->d_name, tmp) == TRUE) {
Chris Allegretta7d97ce72000-11-06 04:04:15 +0000828
829 /* Cool, found a match. Add it to the list
830 * This makes a lot more sense to me (Chris) this way...
831 */
832 tmp2 = NULL;
833 tmp2 = nmalloc(strlen(next->d_name) + 1);
834 strcpy(tmp2, next->d_name);
835 matches[*num_matches] = tmp2;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000836 ++*num_matches;
Chris Allegretta2c975222000-11-15 01:25:42 +0000837
838 /* If there's no more room, bail out */
839 if (*num_matches == BUFSIZ)
840 break;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000841 }
842 }
843
844 return (matches);
845}
846
Chris Allegretta442f2c52000-11-14 17:46:06 +0000847/* This function now has an arg which refers to how much the
Chris Allegretta04d848e2000-11-05 17:54:41 +0000848 * statusbar (place) should be advanced, i.e. the new cursor pos.
849 */
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000850char *input_tab(char *buf, int place, int *lastWasTab, int *newplace)
Chris Allegretta04d848e2000-11-05 17:54:41 +0000851{
852 /* Do TAB completion */
Chris Allegrettaec58a992000-11-05 21:54:23 +0000853 static int num_matches = 0, match_matches = 0;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000854 static char **matches = (char **) NULL;
Chris Allegretta442f2c52000-11-14 17:46:06 +0000855 int pos = place, i = 0, col = 0, editline = 0;
Chris Allegretta04fec912000-11-25 04:43:43 +0000856 int longestname = 0, is_dir = 0;
Chris Allegretta3b0d1442000-11-05 21:56:54 +0000857 char *foo;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000858
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000859 if (*lastWasTab == FALSE) {
Chris Allegretta442f2c52000-11-14 17:46:06 +0000860 char *tmp, *copyto, *matchBuf;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000861
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000862 *lastWasTab = 1;
863
Chris Allegretta04d848e2000-11-05 17:54:41 +0000864 /* Make a local copy of the string -- up to the position of the
865 cursor */
Chris Allegrettae118acc2000-11-06 05:40:03 +0000866 matchBuf = (char *) calloc(strlen(buf) + 2, sizeof(char));
867
Chris Allegretta04d848e2000-11-05 17:54:41 +0000868 strncpy(matchBuf, buf, place);
869 tmp = matchBuf;
870
871 /* skip any leading white space */
Chris Allegretta63a89d32000-11-18 03:05:50 +0000872 while (*tmp && isspace((int) *tmp))
Chris Allegretta04d848e2000-11-05 17:54:41 +0000873 ++tmp;
874
875 /* Free up any memory already allocated */
876 if (matches != NULL) {
Chris Allegretta442f2c52000-11-14 17:46:06 +0000877 for (i = i; i < num_matches; i++)
878 free(matches[i]);
Chris Allegretta04d848e2000-11-05 17:54:41 +0000879 free(matches);
880 matches = (char **) NULL;
Chris Allegretta7d97ce72000-11-06 04:04:15 +0000881 num_matches = 0;
Chris Allegretta04d848e2000-11-05 17:54:41 +0000882 }
883
884 /* If the word starts with `~' and there is no slash in the word,
885 * then try completing this word as a username. */
886
Chris Allegrettabe77c612000-11-24 14:00:16 +0000887 /* FIXME -- this check is broken! */
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000888 if (*tmp == '~' && !strchr(tmp, '/'))
Chris Allegrettabe77c612000-11-24 14:00:16 +0000889 matches = username_tab_completion(tmp, &num_matches);
Chris Allegretta04d848e2000-11-05 17:54:41 +0000890
891 /* Try to match everything in the current working directory that
892 * matches. */
893 if (!matches)
894 matches = cwd_tab_completion(tmp, &num_matches);
895
896 /* Don't leak memory */
897 free(matchBuf);
898
Chris Allegretta442f2c52000-11-14 17:46:06 +0000899#ifdef DEBUG
900 fprintf(stderr, "%d matches found...\n", num_matches);
901#endif
Chris Allegretta04d848e2000-11-05 17:54:41 +0000902 /* Did we find exactly one match? */
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000903 switch (num_matches) {
Chris Allegrettae118acc2000-11-06 05:40:03 +0000904 case 0:
Chris Allegretta24342432000-11-06 05:41:20 +0000905 blank_edit();
Chris Allegretta2c975222000-11-15 01:25:42 +0000906 wrefresh(edit);
Chris Allegrettae118acc2000-11-06 05:40:03 +0000907 break;
908 case 1:
Chris Allegretta442f2c52000-11-14 17:46:06 +0000909
910 buf = nrealloc(buf, strlen(buf) + strlen(matches[0]) + 1);
911
912 if (strcmp(buf, "") && strstr(buf, "/")) {
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000913 for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
914 tmp--);
Chris Allegretta442f2c52000-11-14 17:46:06 +0000915 tmp++;
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000916 } else
Chris Allegretta442f2c52000-11-14 17:46:06 +0000917 tmp = buf;
918
Chris Allegretta04fec912000-11-25 04:43:43 +0000919 if (!strcmp(tmp, matches[0]))
920 is_dir = append_slash_if_dir(buf, lastWasTab, newplace);
921
922 if (is_dir)
923 break;
Chris Allegretta442f2c52000-11-14 17:46:06 +0000924
925 copyto = tmp;
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000926 for (pos = 0; *tmp == matches[0][pos] &&
927 pos <= strlen(matches[0]); pos++)
Chris Allegretta442f2c52000-11-14 17:46:06 +0000928 tmp++;
929
Chris Allegretta2c975222000-11-15 01:25:42 +0000930 /* write out the matched name */
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000931 strncpy(copyto, matches[0], strlen(matches[0]) + 1);
Chris Allegretta442f2c52000-11-14 17:46:06 +0000932 *newplace += strlen(matches[0]) - pos;
933
Chris Allegrettabe77c612000-11-24 14:00:16 +0000934 /* Is it a directory? */
935 append_slash_if_dir(buf, lastWasTab, newplace);
936
Chris Allegrettae118acc2000-11-06 05:40:03 +0000937 break;
938 default:
Chris Allegrettaec58a992000-11-05 21:54:23 +0000939 /* Check to see if all matches share a beginning, and if so
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000940 tack it onto buf and then beep */
Chris Allegrettaec58a992000-11-05 21:54:23 +0000941
Chris Allegretta442f2c52000-11-14 17:46:06 +0000942 if (strcmp(buf, "") && strstr(buf, "/")) {
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000943 for (tmp = buf + strlen(buf); *tmp != '/' && tmp != buf;
944 tmp--);
Chris Allegretta442f2c52000-11-14 17:46:06 +0000945 tmp++;
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000946 } else
Chris Allegretta442f2c52000-11-14 17:46:06 +0000947 tmp = buf;
948
949 for (pos = 0; *tmp == matches[0][pos] && *tmp != 0 &&
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000950 pos <= strlen(matches[0]); pos++)
Chris Allegretta442f2c52000-11-14 17:46:06 +0000951 tmp++;
952
Chris Allegrettaec58a992000-11-05 21:54:23 +0000953 while (1) {
954 match_matches = 0;
955
956 for (i = 0; i < num_matches; i++) {
957 if (matches[i][pos] == 0)
958 break;
959 else if (matches[i][pos] == matches[0][pos])
960 match_matches++;
961 }
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000962 if (match_matches == num_matches &&
963 (i == num_matches || matches[i] != 0)) {
Chris Allegrettaec58a992000-11-05 21:54:23 +0000964 /* All the matches have the same character at pos+1,
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000965 so paste it into buf... */
Chris Allegretta75864952000-11-05 22:48:35 +0000966 buf = nrealloc(buf, strlen(buf) + 2);
Chris Allegretta442f2c52000-11-14 17:46:06 +0000967 strncat(buf, matches[0] + pos, 1);
Chris Allegrettab5b89ae2000-11-14 18:25:26 +0000968 *newplace += 1;
Chris Allegrettaec58a992000-11-05 21:54:23 +0000969 pos++;
Chris Allegretta75864952000-11-05 22:48:35 +0000970 } else {
Chris Allegrettaec58a992000-11-05 21:54:23 +0000971 beep();
972 break;
973 }
974 }
Chris Allegrettae118acc2000-11-06 05:40:03 +0000975 break;
Chris Allegrettaec58a992000-11-05 21:54:23 +0000976 }
Chris Allegretta04d848e2000-11-05 17:54:41 +0000977 } else {
978 /* Ok -- the last char was a TAB. Since they
979 * just hit TAB again, print a list of all the
980 * available choices... */
981 if (matches && num_matches > 0) {
Chris Allegretta04d848e2000-11-05 17:54:41 +0000982
983 /* Blank the edit window, and print the matches out there */
984 blank_edit();
985 wmove(edit, 0, 0);
986
Chris Allegrettaec58a992000-11-05 21:54:23 +0000987 editline = 0;
Chris Allegretta2c975222000-11-15 01:25:42 +0000988
Chris Allegrettaec58a992000-11-05 21:54:23 +0000989 /* Figure out the length of the longest filename */
990 for (i = 0; i < num_matches; i++)
991 if (strlen(matches[i]) > longestname)
992 longestname = strlen(matches[i]);
993
994 if (longestname > COLS - 1)
995 longestname = COLS - 1;
996
Chris Allegretta3b0d1442000-11-05 21:56:54 +0000997 foo = nmalloc(longestname + 5);
998
Chris Allegretta04d848e2000-11-05 17:54:41 +0000999 /* Print the list of matches */
1000 for (i = 0, col = 0; i < num_matches; i++) {
Chris Allegrettab5b89ae2000-11-14 18:25:26 +00001001
Chris Allegrettaec58a992000-11-05 21:54:23 +00001002 /* make each filename shown be the same length as the longest
Chris Allegrettab5b89ae2000-11-14 18:25:26 +00001003 filename, with two spaces at the end */
Chris Allegrettaec58a992000-11-05 21:54:23 +00001004 snprintf(foo, longestname + 1, matches[i]);
1005 while (strlen(foo) < longestname)
1006 strcat(foo, " ");
1007
1008 strcat(foo, " ");
1009
Chris Allegretta442f2c52000-11-14 17:46:06 +00001010 /* Disable el cursor */
1011 curs_set(0);
Chris Allegretta75864952000-11-05 22:48:35 +00001012 /* now, put the match on the screen */
1013 waddnstr(edit, foo, strlen(foo));
1014 col += strlen(foo);
1015
1016 /* And if the next match isn't going to fit on the
1017 line, move to the next one */
Chris Allegrettab5b89ae2000-11-14 18:25:26 +00001018 if (col > (COLS - longestname) && matches[i + 1] != NULL) {
Chris Allegrettaec58a992000-11-05 21:54:23 +00001019 editline++;
1020 wmove(edit, editline, 0);
Chris Allegrettab5b89ae2000-11-14 18:25:26 +00001021 if (editline == editwinrows - 1) {
1022 waddstr(edit, _("(more)"));
1023 break;
1024 }
Chris Allegretta04d848e2000-11-05 17:54:41 +00001025 col = 0;
1026 }
1027 }
Chris Allegretta3b0d1442000-11-05 21:56:54 +00001028 free(foo);
Chris Allegretta04d848e2000-11-05 17:54:41 +00001029 wrefresh(edit);
Chris Allegretta24dd8d62000-11-06 05:45:48 +00001030 } else
1031 beep();
Chris Allegretta04d848e2000-11-05 17:54:41 +00001032
1033 }
1034
1035 edit_refresh();
Chris Allegretta442f2c52000-11-14 17:46:06 +00001036 curs_set(1);
1037 return buf;
Chris Allegretta04d848e2000-11-05 17:54:41 +00001038}
Chris Allegrettabe77c612000-11-24 14:00:16 +00001039#endif