xref: /openbsd-src/usr.bin/mg/fileio.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: fileio.c,v 1.36 2003/05/08 12:37:13 vincent Exp $	*/
2 
3 /*
4  *	POSIX fileio.c
5  */
6 #include	"def.h"
7 
8 static FILE	*ffp;
9 
10 #include <sys/types.h>
11 #include <limits.h>
12 #include <sys/stat.h>
13 #include <sys/dir.h>
14 #include <string.h>
15 #include <fcntl.h>
16 #include <unistd.h>
17 
18 /*
19  * Open a file for reading.
20  */
21 int
22 ffropen(const char *fn, BUFFER *bp)
23 {
24 	struct stat	statbuf;
25 
26 	if ((ffp = fopen(fn, "r")) == NULL)
27 		return (FIOFNF);
28 	if (bp && fstat(fileno(ffp), &statbuf) == 0) {
29 		/* set highorder bit to make sure this isn't all zero */
30 		bp->b_fi.fi_mode = statbuf.st_mode | 0x8000;
31 		bp->b_fi.fi_uid = statbuf.st_uid;
32 		bp->b_fi.fi_gid = statbuf.st_gid;
33 	}
34 	return (FIOSUC);
35 }
36 
37 /*
38  * Open a file for writing.
39  * Return TRUE if all is well, and
40  * FALSE on error (cannot create).
41  */
42 int
43 ffwopen(const char *fn, BUFFER *bp)
44 {
45 	int fd;
46 	mode_t mode = DEFFILEMODE;
47 
48 	if (bp && bp->b_fi.fi_mode)
49 		mode = bp->b_fi.fi_mode & 07777;
50 
51 	fd = open(fn, O_RDWR | O_CREAT | O_TRUNC, mode);
52 	if (fd == -1) {
53 		ffp = NULL;
54 		ewprintf("Cannot open file for writing : %s", strerror(errno));
55 		return (FIOERR);
56 	}
57 
58 	if ((ffp = fdopen(fd, "w")) == NULL) {
59 		ewprintf("Cannot open file for writing : %s", strerror(errno));
60 		close(fd);
61 		return (FIOERR);
62 	}
63 
64 	/*
65 	 * If we have file information, use it.  We don't bother to check for
66 	 * errors, because there's no a lot we can do about it.  Certainly
67 	 * trying to change ownership will fail if we aren't root.  That's
68 	 * probably OK.  If we don't have info, no need to get it, since any
69 	 * future writes will do the same thing.
70 	 */
71 	if (bp && bp->b_fi.fi_mode) {
72 		fchmod(fd, bp->b_fi.fi_mode & 07777);
73 		fchown(fd, bp->b_fi.fi_uid, bp->b_fi.fi_gid);
74 	}
75 	return (FIOSUC);
76 }
77 
78 /*
79  * Close a file.
80  * XXX - Should look at the status.
81  */
82 /* ARGSUSED */
83 int
84 ffclose(BUFFER *bp)
85 {
86 
87 	(void) fclose(ffp);
88 	return (FIOSUC);
89 }
90 
91 /*
92  * Write a buffer to the already
93  * opened file. bp points to the
94  * buffer. Return the status.
95  * Check only at the newline and
96  * end of buffer.
97  */
98 int
99 ffputbuf(BUFFER *bp)
100 {
101 	char   *cp;
102 	char   *cpend;
103 	LINE   *lp;
104 	LINE   *lpend;
105 
106 	lpend = bp->b_linep;
107 	lp = lforw(lpend);
108 	do {
109 		cp = &ltext(lp)[0];		/* beginning of line	 */
110 		cpend = &cp[llength(lp)];	/* end of line		 */
111 		while (cp != cpend) {
112 			putc(*cp, ffp);
113 			cp++;			/* putc may evaluate arguments
114 						   more than once */
115 		}
116 		lp = lforw(lp);
117 		if (lp == lpend)
118 			break;			/* no implied \n on last line */
119 		putc('\n', ffp);
120 	} while (!ferror(ffp));
121 	if (ferror(ffp)) {
122 		ewprintf("Write I/O error");
123 		return FIOERR;
124 	}
125 	return (FIOSUC);
126 }
127 
128 /*
129  * Read a line from a file, and store the bytes
130  * in the supplied buffer. Stop on end of file or end of
131  * line.  When FIOEOF is returned, there is a valid line
132  * of data without the normally implied \n.
133  */
134 int
135 ffgetline(char *buf, int nbuf, int *nbytes)
136 {
137 	int	c, i;
138 
139 	i = 0;
140 	while ((c = getc(ffp)) != EOF && c != '\n') {
141 		buf[i++] = c;
142 		if (i >= nbuf)
143 			return FIOLONG;
144 	}
145 	if (c == EOF && ferror(ffp) != FALSE) {
146 		ewprintf("File read error");
147 		return FIOERR;
148 	}
149 	*nbytes = i;
150 	return c == EOF ? FIOEOF : FIOSUC;
151 }
152 
153 #ifndef NO_BACKUP
154 /*
155  * Make a backup copy of "fname".  On Unix the backup has the same
156  * name as the original file, with a "~" on the end; this seems to
157  * be newest of the new-speak. The error handling is all in "file.c".
158  * We do a copy instead of a rename since otherwise another process
159  * with an open fd will get the backup, not the new file.  This is
160  * a problem when using mg with things like crontab and vipw.
161  */
162 int
163 fbackupfile(const char *fn)
164 {
165 	struct stat	sb;
166 	int		from, to, serrno;
167 	size_t		nread;
168 	char		buf[BUFSIZ];
169 	char		*nname;
170 
171 	if (stat(fn, &sb) == -1) {
172 		ewprintf("Can't stat %s : %s", fn, strerror(errno));
173 		return (FALSE);
174 	}
175 
176 	if (asprintf(&nname, "%s~", fn) == -1) {
177 		ewprintf("Can't allocate temp file name : %s",
178 		    strerror(errno));
179 		return (ABORT);
180 	}
181 
182 	if ((from = open(fn, O_RDONLY)) == -1) {
183 		free(nname);
184 		return (FALSE);
185 	}
186 	to = open(nname, O_WRONLY|O_CREAT|O_TRUNC, (sb.st_mode & 0777));
187 	if (to == -1) {
188 		serrno = errno;
189 		close(from);
190 		free(nname);
191 		errno = serrno;
192 		return (FALSE);
193 	}
194 	while ((nread = read(from, buf, sizeof(buf))) > 0) {
195 		if (write(to, buf, nread) != nread) {
196 		    nread = -1;
197 		    break;
198 		}
199 	}
200 	serrno = errno;
201 	close(from);
202 	close(to);
203 	if (nread == -1) {
204 		if (unlink(nname) == -1)
205 			ewprintf("Can't unlink temp : %s", strerror(errno));
206 	}
207 	free(nname);
208 	errno = serrno;
209 
210 	return (nread == -1 ? FALSE : TRUE);
211 }
212 #endif
213 
214 /*
215  * The string "fn" is a file name.
216  * Perform any required appending of directory name or case adjustments.
217  * If NO_DIR is not defined, the same file should be refered to even if the
218  * working directory changes.
219  */
220 #ifdef SYMBLINK
221 #include <sys/types.h>
222 #include <sys/stat.h>
223 #ifndef MAXLINK
224 #define MAXLINK 8		/* maximum symbolic links to follow */
225 #endif
226 #endif
227 #include <pwd.h>
228 #ifndef NO_DIR
229 extern char	*wdir;
230 #endif
231 
232 char *
233 adjustname(const char *fn)
234 {
235 	static char fnb[MAXPATHLEN];
236 	const char *cp;
237 	char user[LOGIN_NAME_MAX + 1], path[MAXPATHLEN];
238 	int len;
239 
240 	path[0] = '\0';
241 	/* first handle tilde expansion */
242 	if (fn[0] == '~') {
243 		struct passwd *pw;
244 
245 		cp = strchr(fn, '/');
246 		if (cp == NULL)
247 			cp = fn + strlen(fn); /* point to the NUL byte */
248 
249 		if ((cp - &fn[1]) > LOGIN_NAME_MAX) {
250 			ewprintf("login name too long");
251 			return (NULL);
252 		}
253 		if (cp == &fn[1]) /* ~/ */
254 			strlcpy(user, getlogin(), sizeof user);
255 		else
256 			strlcpy(user, &fn[1], cp - &fn[1] + 1);
257 		pw = getpwnam(user);
258 		if (pw == NULL) {
259 			ewprintf("unknown user %s", user);
260 			return (NULL);
261 		}
262 		strlcpy(path, pw->pw_dir, sizeof path - 1);
263 		len = strlen(path);
264 		if (path[len] != '/') {
265 			path[len] = '/';
266 			path[len + 1] = '\0';
267 		}
268 		fn = cp;
269 		if (*fn == '/')
270 			fn++;
271 	}
272 	strlcat(path, fn, sizeof path);
273 
274 	return (realpath(path, fnb));
275 }
276 
277 #ifndef NO_STARTUP
278 /*
279  * Find a startup file for the user and return its name. As a service
280  * to other pieces of code that may want to find a startup file (like
281  * the terminal driver in particular), accepts a suffix to be appended
282  * to the startup file name.
283  */
284 char *
285 startupfile(char *suffix)
286 {
287 	static char	file[NFILEN];
288 	char		*home;
289 
290 	if ((home = getenv("HOME")) == NULL || *home == '\0')
291 		goto nohome;
292 
293 	if (suffix == NULL) {
294 		if (snprintf(file, sizeof(file), "%s/.mg", home)
295 		    >= sizeof(file))
296 			return (NULL);
297 	} else {
298 		if (snprintf(file, sizeof(file), "%s/.mg-%s", home, suffix)
299 		    >= sizeof(file))
300 			return (NULL);
301 	}
302 
303 	if (access(file, R_OK) == 0)
304 		return (file);
305 nohome:
306 #ifdef STARTUPFILE
307 	if (suffix == NULL) {
308 		if (snprintf(file, sizeof(file), "%s", STARTUPFILE)
309 		    >= sizeof(file))
310 			return (NULL);
311 	} else {
312 		if (snprintf(file, sizeof(file), "%s%s", STARTUPFILE, suffix)
313 		    >= sizeof(file))
314 			return (NULL);
315 	}
316 
317 	if (access(file, R_OK) == 0)
318 		return (file);
319 #endif
320 	return (NULL);
321 }
322 #endif
323 
324 #ifndef NO_DIRED
325 #include <sys/wait.h>
326 #include "kbd.h"
327 
328 int
329 copy(char *frname, char *toname)
330 {
331 	int ifd, ofd, n;
332 	char buf[BUFSIZ];
333 	mode_t mode = DEFFILEMODE;	/* XXX?? */
334 	struct stat orig;
335 
336 	if ((ifd = open(frname, O_RDONLY)) == -1)
337 		return (FALSE);
338 	if (fstat(ifd, &orig) == -1) {
339 		ewprintf("fstat: %s", strerror(errno));
340 		close(ifd);
341 		return (FALSE);
342 	}
343 
344 	if ((ofd = open(toname, O_WRONLY|O_CREAT|O_TRUNC, mode)) == -1) {
345 		close(ifd);
346 		return (FALSE);
347 	}
348 	while ((n = read(ifd, buf, sizeof buf)) > 0) {
349 		if (write(ofd, buf, n) != n) {
350 			ewprintf("write error : %s", strerror(errno));
351 			break;
352 		}
353 	}
354 	if (fchmod(ofd, orig.st_mode) == -1)
355 		ewprintf("Cannot set original mode : %s", strerror(errno));
356 
357 	if (n == -1) {
358 		ewprintf("Read error : %s", strerror(errno));
359 		close(ifd);
360 		close(ofd);
361 		return (FALSE);
362 	}
363 	/*
364 	 * It is "normal" for this to fail since we can't guarantee that
365 	 * we will be running as root
366 	 */
367 	if (fchown(ofd, orig.st_uid, orig.st_gid) && errno != EPERM)
368 		ewprintf("Cannot set owner : %s", strerror(errno));
369 
370 	(void) close(ifd);
371 	(void) close(ofd);
372 
373 	return (TRUE);
374 }
375 
376 /*
377  * dirname needs to have enough place to store an additional '/'.
378  */
379 BUFFER *
380 dired_(char *dirname)
381 {
382 	BUFFER	*bp;
383 	FILE	*dirpipe;
384 	char	line[256];
385 	int	len;
386 
387 	if ((dirname = adjustname(dirname)) == NULL) {
388 		ewprintf("Bad directory name");
389 		return NULL;
390 	}
391 	/* this should not be done, instead adjustname() should get a flag */
392 	len = strlen(dirname);
393 	if (dirname[len - 1] != '/') {
394 		dirname[len++] = '/';
395 		dirname[len] = '\0';
396 	}
397 	if ((bp = findbuffer(dirname)) == NULL) {
398 		ewprintf("Could not create buffer");
399 		return NULL;
400 	}
401 	if (bclear(bp) != TRUE)
402 		return NULL;
403 	bp->b_flag |= BFREADONLY;
404 	if (snprintf(line, sizeof(line), "ls -al %s", dirname) >= sizeof(line)){
405 		ewprintf("Path too long");
406 		return NULL;
407 	}
408 	if ((dirpipe = popen(line, "r")) == NULL) {
409 		ewprintf("Problem opening pipe to ls");
410 		return NULL;
411 	}
412 	line[0] = line[1] = ' ';
413 	while (fgets(&line[2], sizeof(line) - 2, dirpipe) != NULL) {
414 		line[strlen(line) - 1] = '\0';	/* remove ^J	 */
415 		(void) addline(bp, line);
416 	}
417 	if (pclose(dirpipe) == -1) {
418 		ewprintf("Problem closing pipe to ls");
419 		return NULL;
420 	}
421 	bp->b_dotp = lforw(bp->b_linep);	/* go to first line */
422 	(void) strlcpy(bp->b_fname, dirname, sizeof bp->b_fname);
423 	if ((bp->b_modes[0] = name_mode("dired")) == NULL) {
424 		bp->b_modes[0] = name_mode("fundamental");
425 		ewprintf("Could not find mode dired");
426 		return NULL;
427 	}
428 	bp->b_nmodes = 0;
429 	return bp;
430 }
431 
432 #define NAME_FIELD	8
433 
434 int
435 d_makename(LINE *lp, char *fn, int len)
436 {
437 	int i;
438 	char *p, *np;
439 
440 	strlcpy(fn, curbp->b_fname, len);
441 	p = lp->l_text;
442 	for (i = 0; i < NAME_FIELD; i++) {
443 		np = strpbrk(p, "\t ");
444 		if (np == NULL)
445 			return ABORT;
446 		p = np + 1;
447 		while (*p != '\0' && strchr("\t ", *p))
448 			p++;
449 	}
450 	strlcat(fn, p, len);
451 	return lgetc(lp, 2) == 'd';
452 }
453 #endif				/* NO_DIRED */
454 
455 struct filelist {
456 	LIST	fl_l;
457 	char	fl_name[NFILEN + 2];
458 };
459 
460 /*
461  * return list of file names that match the name in buf.
462  */
463 
464 LIST *
465 make_file_list(char *buf)
466 {
467 	char		*dir, *file, *cp;
468 	int		len, preflen;
469 	DIR		*dirp;
470 	struct dirent	*dent;
471 	LIST		*last;
472 	struct filelist *current;
473 	char		prefixx[NFILEN + 1];
474 
475 	/*
476 	 * We need three different strings: dir - the name of the directory
477 	 * containing what the user typed. Must be a real unix file name,
478 	 * e.g. no ~user, etc..  Must not end in /. prefix - the portion of
479 	 * what the user typed that is before the names we are going to find
480 	 * in the directory.  Must have a trailing / if the user typed it.
481 	 * names from the directory. we open dir, and return prefix
482 	 * concatenated with names.
483 	 */
484 
485 	/* first we get a directory name we can look up */
486 	/*
487 	 * Names ending in . are potentially odd, because adjustname will
488 	 * treat foo/.. as a reference to another directory, whereas we are
489 	 * interested in names starting with ..
490 	 */
491 	len = strlen(buf);
492 	if (buf[len - 1] == '.') {
493 		buf[len - 1] = 'x';
494 		dir = adjustname(buf);
495 		buf[len - 1] = '.';
496 	} else
497 		dir = adjustname(buf);
498 	if (dir == NULL)
499 		return (NULL);
500 	/*
501 	 * If the user typed a trailing / or the empty string
502 	 * he wants us to use his file spec as a directory name.
503 	 */
504 	if (buf[0] && buf[strlen(buf) - 1] != '/') {
505 		file = strrchr(dir, '/');
506 		if (file) {
507 			*file = 0;
508 			if (*dir == 0)
509 				dir = "/";
510 		} else {
511 			return (NULL);
512 		}
513 	}
514 	/* Now we get the prefix of the name the user typed. */
515 	strlcpy(prefixx, buf, sizeof prefixx);
516 	cp = strrchr(prefixx, '/');
517 	if (cp == NULL)
518 		prefixx[0] = 0;
519 	else
520 		cp[1] = 0;
521 
522 	preflen = strlen(prefixx);
523 	/* cp is the tail of buf that really needs to be compared */
524 	cp = buf + preflen;
525 	len = strlen(cp);
526 
527 	/*
528 	 * Now make sure that file names will fit in the buffers allocated.
529 	 * SV files are fairly short.  For BSD, something more general would
530 	 * be required.
531 	 */
532 	if ((preflen + MAXNAMLEN) > NFILEN)
533 		return (NULL);
534 
535 	/* loop over the specified directory, making up the list of files */
536 
537 	/*
538 	 * Note that it is worth our time to filter out names that don't
539 	 * match, even though our caller is going to do so again, and to
540 	 * avoid doing the stat if completion is being done, because stat'ing
541 	 * every file in the directory is relatively expensive.
542 	 */
543 
544 	dirp = opendir(dir);
545 	if (dirp == NULL)
546 		return (NULL);
547 	last = NULL;
548 
549 	while ((dent = readdir(dirp)) != NULL) {
550 		int isdir;
551 
552 		if (dent->d_namlen < len || memcmp(cp, dent->d_name, len) != 0)
553 			continue;
554 
555 		isdir = 0;
556 		if (dent->d_type == DT_DIR) {
557 			isdir = 1;
558 		} else if (dent->d_type == DT_LNK ||
559 			    dent->d_type == DT_UNKNOWN) {
560 			struct stat	statbuf;
561 			char		statname[NFILEN + 2];
562 
563 			statbuf.st_mode = 0;
564 			if (snprintf(statname, sizeof(statname), "%s/%s",
565 			    dir, dent->d_name) > sizeof(statname) - 1) {
566 				continue;
567 			}
568 			if (stat(statname, &statbuf) < 0)
569 				continue;
570 			if (statbuf.st_mode & S_IFDIR)
571 				isdir = 1;
572 		}
573 
574 		current = malloc(sizeof(struct filelist));
575 		if (current == NULL)
576 			break;
577 
578 		if (snprintf(current->fl_name, sizeof(current->fl_name),
579 		    "%s%s%s", prefixx, dent->d_name, isdir ? "/" : "")
580 		    >= sizeof(current->fl_name)) {
581 			free(current);
582 			continue;
583 		}
584 		current->fl_l.l_next = last;
585 		current->fl_l.l_name = current->fl_name;
586 		last = (LIST *) current;
587 	}
588 	closedir(dirp);
589 
590 	return (last);
591 }
592