GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mg/fileio.c Lines: 0 323 0.0 %
Date: 2016-12-06 Branches: 0 248 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: fileio.c,v 1.101 2016/07/04 03:24:48 guenther Exp $	*/
2
3
/* This file is in the public domain. */
4
5
/*
6
 *	POSIX fileio.c
7
 */
8
9
#include <sys/queue.h>
10
#include <sys/resource.h>
11
#include <sys/stat.h>
12
#include <sys/time.h>
13
#include <sys/types.h>
14
#include <sys/wait.h>
15
#include <dirent.h>
16
#include <errno.h>
17
#include <fcntl.h>
18
#include <limits.h>
19
#include <pwd.h>
20
#include <signal.h>
21
#include <stdio.h>
22
#include <stdlib.h>
23
#include <string.h>
24
#include <unistd.h>
25
26
#include "def.h"
27
#include "kbd.h"
28
#include "pathnames.h"
29
30
static char *bkuplocation(const char *);
31
static int   bkupleavetmp(const char *);
32
33
static char *bkupdir;
34
static int   leavetmp = 0;	/* 1 = leave any '~' files in tmp dir */
35
36
/*
37
 * Open a file for reading.
38
 */
39
int
40
ffropen(FILE ** ffp, const char *fn, struct buffer *bp)
41
{
42
	if ((*ffp = fopen(fn, "r")) == NULL) {
43
		if (errno == ENOENT)
44
			return (FIOFNF);
45
		return (FIOERR);
46
	}
47
48
	/* If 'fn' is a directory open it with dired. */
49
	if (fisdir(fn) == TRUE)
50
		return (FIODIR);
51
52
	ffstat(*ffp, bp);
53
54
	return (FIOSUC);
55
}
56
57
/*
58
 * Update stat/dirty info
59
 */
60
void
61
ffstat(FILE *ffp, struct buffer *bp)
62
{
63
	struct stat	sb;
64
65
	if (bp && fstat(fileno(ffp), &sb) == 0) {
66
		/* set highorder bit to make sure this isn't all zero */
67
		bp->b_fi.fi_mode = sb.st_mode | 0x8000;
68
		bp->b_fi.fi_uid = sb.st_uid;
69
		bp->b_fi.fi_gid = sb.st_gid;
70
		bp->b_fi.fi_mtime = sb.st_mtimespec;
71
		/* Clear the ignore flag */
72
		bp->b_flag &= ~(BFIGNDIRTY | BFDIRTY);
73
	}
74
}
75
76
/*
77
 * Update the status/dirty info. If there is an error,
78
 * there's not a lot we can do.
79
 */
80
int
81
fupdstat(struct buffer *bp)
82
{
83
	FILE *ffp;
84
85
	if ((ffp = fopen(bp->b_fname, "r")) == NULL) {
86
		if (errno == ENOENT)
87
			return (FIOFNF);
88
		return (FIOERR);
89
	}
90
	ffstat(ffp, bp);
91
	(void)ffclose(ffp, bp);
92
	return (FIOSUC);
93
}
94
95
/*
96
 * Open a file for writing.
97
 */
98
int
99
ffwopen(FILE ** ffp, const char *fn, struct buffer *bp)
100
{
101
	int	fd;
102
	mode_t	fmode = DEFFILEMODE;
103
104
	if (bp && bp->b_fi.fi_mode)
105
		fmode = bp->b_fi.fi_mode & 07777;
106
107
	fd = open(fn, O_RDWR | O_CREAT | O_TRUNC, fmode);
108
	if (fd == -1) {
109
		ffp = NULL;
110
		dobeep();
111
		ewprintf("Cannot open file for writing : %s", strerror(errno));
112
		return (FIOERR);
113
	}
114
115
	if ((*ffp = fdopen(fd, "w")) == NULL) {
116
		dobeep();
117
		ewprintf("Cannot open file for writing : %s", strerror(errno));
118
		close(fd);
119
		return (FIOERR);
120
	}
121
122
	/*
123
	 * If we have file information, use it.  We don't bother to check for
124
	 * errors, because there's no a lot we can do about it.  Certainly
125
	 * trying to change ownership will fail if we aren't root.  That's
126
	 * probably OK.  If we don't have info, no need to get it, since any
127
	 * future writes will do the same thing.
128
	 */
129
	if (bp && bp->b_fi.fi_mode) {
130
		fchmod(fd, bp->b_fi.fi_mode & 07777);
131
		fchown(fd, bp->b_fi.fi_uid, bp->b_fi.fi_gid);
132
	}
133
	return (FIOSUC);
134
}
135
136
/*
137
 * Close a file.
138
 */
139
/* ARGSUSED */
140
int
141
ffclose(FILE *ffp, struct buffer *bp)
142
{
143
	if (fclose(ffp) == 0)
144
		return (FIOSUC);
145
	return (FIOERR);
146
}
147
148
/*
149
 * Write a buffer to the already opened file. bp points to the
150
 * buffer. Return the status.
151
 */
152
int
153
ffputbuf(FILE *ffp, struct buffer *bp)
154
{
155
	struct line   *lp, *lpend;
156
157
	lpend = bp->b_headp;
158
	for (lp = lforw(lpend); lp != lpend; lp = lforw(lp)) {
159
		if (fwrite(ltext(lp), 1, llength(lp), ffp) != llength(lp)) {
160
			dobeep();
161
			ewprintf("Write I/O error");
162
			return (FIOERR);
163
		}
164
		if (lforw(lp) != lpend)		/* no implied \n on last line */
165
			putc('\n', ffp);
166
	}
167
	/*
168
	 * XXX should be variable controlled (once we have variables)
169
	 */
170
	if (llength(lback(lpend)) != 0) {
171
		if (eyorn("No newline at end of file, add one") == TRUE) {
172
			lnewline_at(lback(lpend), llength(lback(lpend)));
173
			putc('\n', ffp);
174
		}
175
	}
176
	return (FIOSUC);
177
}
178
179
/*
180
 * Read a line from a file, and store the bytes
181
 * in the supplied buffer. Stop on end of file or end of
182
 * line.  When FIOEOF is returned, there is a valid line
183
 * of data without the normally implied \n.
184
 * If the line length exceeds nbuf, FIOLONG is returned.
185
 */
186
int
187
ffgetline(FILE *ffp, char *buf, int nbuf, int *nbytes)
188
{
189
	int	c, i;
190
191
	i = 0;
192
	while ((c = getc(ffp)) != EOF && c != '\n') {
193
		buf[i++] = c;
194
		if (i >= nbuf)
195
			return (FIOLONG);
196
	}
197
	if (c == EOF && ferror(ffp) != FALSE) {
198
		dobeep();
199
		ewprintf("File read error");
200
		return (FIOERR);
201
	}
202
	*nbytes = i;
203
	return (c == EOF ? FIOEOF : FIOSUC);
204
}
205
206
/*
207
 * Make a backup copy of "fname".  On Unix the backup has the same
208
 * name as the original file, with a "~" on the end; this seems to
209
 * be newest of the new-speak. The error handling is all in "file.c".
210
 * We do a copy instead of a rename since otherwise another process
211
 * with an open fd will get the backup, not the new file.  This is
212
 * a problem when using mg with things like crontab and vipw.
213
 */
214
int
215
fbackupfile(const char *fn)
216
{
217
	struct stat	 sb;
218
	int		 from, to, serrno;
219
	ssize_t		 nread;
220
	char		 buf[BUFSIZ];
221
	char		*nname, *tname, *bkpth;
222
223
	if (stat(fn, &sb) == -1) {
224
		dobeep();
225
		ewprintf("Can't stat %s : %s", fn, strerror(errno));
226
		return (FALSE);
227
	}
228
229
	if ((bkpth = bkuplocation(fn)) == NULL)
230
		return (FALSE);
231
232
	if (asprintf(&nname, "%s~", bkpth) == -1) {
233
		dobeep();
234
		ewprintf("Can't allocate backup file name : %s", strerror(errno));
235
		free(bkpth);
236
		return (ABORT);
237
	}
238
	if (asprintf(&tname, "%s.XXXXXXXXXX", bkpth) == -1) {
239
		dobeep();
240
		ewprintf("Can't allocate temp file name : %s", strerror(errno));
241
		free(bkpth);
242
		free(nname);
243
		return (ABORT);
244
	}
245
	free(bkpth);
246
247
	if ((from = open(fn, O_RDONLY)) == -1) {
248
		free(nname);
249
		free(tname);
250
		return (FALSE);
251
	}
252
	to = mkstemp(tname);
253
	if (to == -1) {
254
		serrno = errno;
255
		close(from);
256
		free(nname);
257
		free(tname);
258
		errno = serrno;
259
		return (FALSE);
260
	}
261
	while ((nread = read(from, buf, sizeof(buf))) > 0) {
262
		if (write(to, buf, (size_t)nread) != nread) {
263
			nread = -1;
264
			break;
265
		}
266
	}
267
	serrno = errno;
268
	(void) fchmod(to, (sb.st_mode & 0777));
269
270
	/* copy the mtime to the backupfile */
271
	struct timespec new_times[2];
272
	new_times[0] = sb.st_atim;
273
	new_times[1] = sb.st_mtim;
274
	futimens(to, new_times);
275
276
	close(from);
277
	close(to);
278
	if (nread == -1) {
279
		if (unlink(tname) == -1)
280
			ewprintf("Can't unlink temp : %s", strerror(errno));
281
	} else {
282
		if (rename(tname, nname) == -1) {
283
			ewprintf("Can't rename temp : %s", strerror(errno));
284
			(void) unlink(tname);
285
			nread = -1;
286
		}
287
	}
288
	free(nname);
289
	free(tname);
290
	errno = serrno;
291
292
	return (nread == -1 ? FALSE : TRUE);
293
}
294
295
/*
296
 * Convert "fn" to a canonicalized absolute filename, replacing
297
 * a leading ~/ with the user's home dir, following symlinks, and
298
 * remove all occurrences of /./ and /../
299
 */
300
char *
301
adjustname(const char *fn, int slashslash)
302
{
303
	static char	 fnb[PATH_MAX];
304
	const char	*cp, *ep = NULL;
305
	char		*path;
306
307
	if (slashslash == TRUE) {
308
		cp = fn + strlen(fn) - 1;
309
		for (; cp >= fn; cp--) {
310
			if (ep && (*cp == '/')) {
311
				fn = ep;
312
				break;
313
			}
314
			if (*cp == '/' || *cp == '~')
315
				ep = cp;
316
			else
317
				ep = NULL;
318
		}
319
	}
320
	if ((path = expandtilde(fn)) == NULL)
321
		return (NULL);
322
323
	if (realpath(path, fnb) == NULL)
324
		(void)strlcpy(fnb, path, sizeof(fnb));
325
326
	free(path);
327
	return (fnb);
328
}
329
330
/*
331
 * Find a startup file for the user and return its name. As a service
332
 * to other pieces of code that may want to find a startup file (like
333
 * the terminal driver in particular), accepts a suffix to be appended
334
 * to the startup file name.
335
 */
336
char *
337
startupfile(char *suffix)
338
{
339
	static char	 file[NFILEN];
340
	char		*home;
341
	int		 ret;
342
343
	if ((home = getenv("HOME")) == NULL || *home == '\0')
344
		goto nohome;
345
346
	if (suffix == NULL) {
347
		ret = snprintf(file, sizeof(file), _PATH_MG_STARTUP, home);
348
		if (ret < 0 || ret >= sizeof(file))
349
			return (NULL);
350
	} else {
351
		ret = snprintf(file, sizeof(file), _PATH_MG_TERM, home, suffix);
352
		if (ret < 0 || ret >= sizeof(file))
353
			return (NULL);
354
	}
355
356
	if (access(file, R_OK) == 0)
357
		return (file);
358
nohome:
359
#ifdef STARTUPFILE
360
	if (suffix == NULL) {
361
		ret = snprintf(file, sizeof(file), "%s", STARTUPFILE);
362
		if (ret < 0 || ret >= sizeof(file))
363
			return (NULL);
364
	} else {
365
		ret = snprintf(file, sizeof(file), "%s%s", STARTUPFILE,
366
		    suffix);
367
		if (ret < 0 || ret >= sizeof(file))
368
			return (NULL);
369
	}
370
371
	if (access(file, R_OK) == 0)
372
		return (file);
373
#endif /* STARTUPFILE */
374
	return (NULL);
375
}
376
377
int
378
copy(char *frname, char *toname)
379
{
380
	int	ifd, ofd;
381
	char	buf[BUFSIZ];
382
	mode_t	fmode = DEFFILEMODE;	/* XXX?? */
383
	struct	stat orig;
384
	ssize_t	sr;
385
386
	if ((ifd = open(frname, O_RDONLY)) == -1)
387
		return (FALSE);
388
	if (fstat(ifd, &orig) == -1) {
389
		dobeep();
390
		ewprintf("fstat: %s", strerror(errno));
391
		close(ifd);
392
		return (FALSE);
393
	}
394
395
	if ((ofd = open(toname, O_WRONLY|O_CREAT|O_TRUNC, fmode)) == -1) {
396
		close(ifd);
397
		return (FALSE);
398
	}
399
	while ((sr = read(ifd, buf, sizeof(buf))) > 0) {
400
		if (write(ofd, buf, (size_t)sr) != sr) {
401
			ewprintf("write error : %s", strerror(errno));
402
			break;
403
		}
404
	}
405
	if (fchmod(ofd, orig.st_mode) == -1)
406
		ewprintf("Cannot set original mode : %s", strerror(errno));
407
408
	if (sr == -1) {
409
		ewprintf("Read error : %s", strerror(errno));
410
		close(ifd);
411
		close(ofd);
412
		return (FALSE);
413
	}
414
	/*
415
	 * It is "normal" for this to fail since we can't guarantee that
416
	 * we will be running as root.
417
	 */
418
	if (fchown(ofd, orig.st_uid, orig.st_gid) && errno != EPERM)
419
		ewprintf("Cannot set owner : %s", strerror(errno));
420
421
	(void) close(ifd);
422
	(void) close(ofd);
423
424
	return (TRUE);
425
}
426
427
/*
428
 * return list of file names that match the name in buf.
429
 */
430
struct list *
431
make_file_list(char *buf)
432
{
433
	char		*dir, *file, *cp;
434
	size_t		 len, preflen;
435
	int		 ret;
436
	DIR		*dirp;
437
	struct dirent	*dent;
438
	struct list	*last, *current;
439
	char		 fl_name[NFILEN + 2];
440
	char		 prefixx[NFILEN + 1];
441
442
	/*
443
	 * We need three different strings:
444
445
	 * dir - the name of the directory containing what the user typed.
446
	 *  Must be a real unix file name, e.g. no ~user, etc..
447
	 *  Must not end in /.
448
	 * prefix - the portion of what the user typed that is before the
449
	 *  names we are going to find in the directory.  Must have a
450
	 * trailing / if the user typed it.
451
	 * names from the directory - We open dir, and return prefix
452
	 * concatenated with names.
453
	 */
454
455
	/* first we get a directory name we can look up */
456
	/*
457
	 * Names ending in . are potentially odd, because adjustname will
458
	 * treat foo/bar/.. as a foo/, whereas we are
459
	 * interested in names starting with ..
460
	 */
461
	len = strlen(buf);
462
	if (len && buf[len - 1] == '.') {
463
		buf[len - 1] = 'x';
464
		dir = adjustname(buf, TRUE);
465
		buf[len - 1] = '.';
466
	} else
467
		dir = adjustname(buf, TRUE);
468
	if (dir == NULL)
469
		return (NULL);
470
	/*
471
	 * If the user typed a trailing / or the empty string
472
	 * he wants us to use his file spec as a directory name.
473
	 */
474
	if (len && buf[len - 1] != '/') {
475
		file = strrchr(dir, '/');
476
		if (file) {
477
			*file = '\0';
478
			if (*dir == '\0')
479
				dir = "/";
480
		} else
481
			return (NULL);
482
	}
483
	/* Now we get the prefix of the name the user typed. */
484
	if (strlcpy(prefixx, buf, sizeof(prefixx)) >= sizeof(prefixx))
485
		return (NULL);
486
	cp = strrchr(prefixx, '/');
487
	if (cp == NULL)
488
		prefixx[0] = '\0';
489
	else
490
		cp[1] = '\0';
491
492
	preflen = strlen(prefixx);
493
	/* cp is the tail of buf that really needs to be compared. */
494
	cp = buf + preflen;
495
	len = strlen(cp);
496
497
	/*
498
	 * Now make sure that file names will fit in the buffers allocated.
499
	 * SV files are fairly short.  For BSD, something more general would
500
	 * be required.
501
	 */
502
	if (preflen > NFILEN - MAXNAMLEN)
503
		return (NULL);
504
505
	/* loop over the specified directory, making up the list of files */
506
507
	/*
508
	 * Note that it is worth our time to filter out names that don't
509
	 * match, even though our caller is going to do so again, and to
510
	 * avoid doing the stat if completion is being done, because stat'ing
511
	 * every file in the directory is relatively expensive.
512
	 */
513
514
	dirp = opendir(dir);
515
	if (dirp == NULL)
516
		return (NULL);
517
	last = NULL;
518
519
	while ((dent = readdir(dirp)) != NULL) {
520
		int isdir;
521
		if (strncmp(cp, dent->d_name, len) != 0)
522
			continue;
523
		isdir = 0;
524
		if (dent->d_type == DT_DIR) {
525
			isdir = 1;
526
		} else if (dent->d_type == DT_LNK ||
527
			    dent->d_type == DT_UNKNOWN) {
528
			struct stat	statbuf;
529
530
			if (fstatat(dirfd(dirp), dent->d_name, &statbuf, 0) < 0)
531
				continue;
532
			if (S_ISDIR(statbuf.st_mode))
533
				isdir = 1;
534
		}
535
536
		if ((current = malloc(sizeof(struct list))) == NULL) {
537
			free_file_list(last);
538
			closedir(dirp);
539
			return (NULL);
540
		}
541
		ret = snprintf(fl_name, sizeof(fl_name),
542
		    "%s%s%s", prefixx, dent->d_name, isdir ? "/" : "");
543
		if (ret < 0 || ret >= sizeof(fl_name)) {
544
			free(current);
545
			continue;
546
		}
547
		current->l_next = last;
548
		current->l_name = strdup(fl_name);
549
		last = current;
550
	}
551
	closedir(dirp);
552
553
	return (last);
554
}
555
556
/*
557
 * Test if a supplied filename refers to a directory
558
 * Returns ABORT on error, TRUE if directory. FALSE otherwise
559
 */
560
int
561
fisdir(const char *fname)
562
{
563
	struct stat	statbuf;
564
565
	if (stat(fname, &statbuf) != 0)
566
		return (ABORT);
567
568
	if (S_ISDIR(statbuf.st_mode))
569
		return (TRUE);
570
571
	return (FALSE);
572
}
573
574
/*
575
 * Check the mtime of the supplied filename.
576
 * Return TRUE if last mtime matches, FALSE if not,
577
 * If the stat fails, return TRUE and try the save anyway
578
 */
579
int
580
fchecktime(struct buffer *bp)
581
{
582
	struct stat sb;
583
584
	if (stat(bp->b_fname, &sb) == -1)
585
		return (TRUE);
586
587
	if (bp->b_fi.fi_mtime.tv_sec != sb.st_mtimespec.tv_sec ||
588
	    bp->b_fi.fi_mtime.tv_nsec != sb.st_mtimespec.tv_nsec)
589
		return (FALSE);
590
591
	return (TRUE);
592
593
}
594
595
/*
596
 * Location of backup file. This function creates the correct path.
597
 */
598
static char *
599
bkuplocation(const char *fn)
600
{
601
	struct stat sb;
602
	char *ret;
603
604
	if (bkupdir != NULL && (stat(bkupdir, &sb) == 0) &&
605
	    S_ISDIR(sb.st_mode) && !bkupleavetmp(fn)) {
606
		char fname[NFILEN];
607
		const char *c;
608
		int i = 0, len;
609
610
		c = fn;
611
		len = strlen(bkupdir);
612
613
		while (*c != '\0') {
614
			/* Make sure we don't go over combined:
615
		 	* strlen(bkupdir + '/' + fname + '\0')
616
		 	*/
617
			if (i >= NFILEN - len - 1)
618
				return (NULL);
619
			if (*c == '/') {
620
				fname[i] = '!';
621
			} else if (*c == '!') {
622
				if (i >= NFILEN - len - 2)
623
					return (NULL);
624
				fname[i++] = '!';
625
				fname[i] = '!';
626
			} else
627
				fname[i] = *c;
628
			i++;
629
			c++;
630
		}
631
		fname[i] = '\0';
632
		if (asprintf(&ret, "%s/%s", bkupdir, fname) == -1)
633
			return (NULL);
634
635
	} else if ((ret = strndup(fn, NFILEN)) == NULL)
636
		return (NULL);
637
638
	return (ret);
639
}
640
641
int
642
backuptohomedir(int f, int n)
643
{
644
	const char	*c = _PATH_MG_DIR;
645
	char		*p;
646
647
	if (bkupdir == NULL) {
648
		p = adjustname(c, TRUE);
649
		bkupdir = strndup(p, NFILEN);
650
		if (bkupdir == NULL)
651
			return(FALSE);
652
653
		if (mkdir(bkupdir, 0700) == -1 && errno != EEXIST) {
654
			free(bkupdir);
655
			bkupdir = NULL;
656
		}
657
	} else {
658
		free(bkupdir);
659
		bkupdir = NULL;
660
	}
661
662
	return (TRUE);
663
}
664
665
/*
666
 * For applications that use mg as the editor and have a desire to keep
667
 * '~' files in the TMPDIR, toggle the location: /tmp | ~/.mg.d
668
 */
669
int
670
toggleleavetmp(int f, int n)
671
{
672
	leavetmp = !leavetmp;
673
674
	return (TRUE);
675
}
676
677
/*
678
 * Returns TRUE if fn is located in the temp directory and we want to save
679
 * those backups there.
680
 */
681
int
682
bkupleavetmp(const char *fn)
683
{
684
	char	*tmpdir, *tmp = NULL;
685
686
	if (!leavetmp)
687
		return(FALSE);
688
689
	if((tmpdir = getenv("TMPDIR")) != NULL && *tmpdir != '\0') {
690
		tmp = strstr(fn, tmpdir);
691
		if (tmp == fn)
692
			return (TRUE);
693
694
		return (FALSE);
695
	}
696
697
	tmp = strstr(fn, "/tmp");
698
	if (tmp == fn)
699
		return (TRUE);
700
701
	return (FALSE);
702
}
703
704
/*
705
 * Expand file names beginning with '~' if appropriate:
706
 *   1, if ./~fn exists, continue without expanding tilde.
707
 *   2, else, if username 'fn' exists, expand tilde with home directory path.
708
 *   3, otherwise, continue and create new buffer called ~fn.
709
 */
710
char *
711
expandtilde(const char *fn)
712
{
713
	struct passwd	*pw;
714
	struct stat	 statbuf;
715
	const char	*cp;
716
	char		 user[LOGIN_NAME_MAX], path[NFILEN];
717
	char		*un, *ret;
718
	size_t		 ulen, plen;
719
720
	path[0] = '\0';
721
722
	if (fn[0] != '~' || stat(fn, &statbuf) == 0) {
723
		if ((ret = strndup(fn, NFILEN)) == NULL)
724
			return (NULL);
725
		return(ret);
726
	}
727
	cp = strchr(fn, '/');
728
	if (cp == NULL)
729
		cp = fn + strlen(fn); /* point to the NUL byte */
730
	ulen = cp - &fn[1];
731
	if (ulen >= sizeof(user)) {
732
		if ((ret = strndup(fn, NFILEN)) == NULL)
733
			return (NULL);
734
		return(ret);
735
	}
736
	if (ulen == 0) { /* ~/ or ~ */
737
		if ((un = getlogin()) != NULL)
738
			(void)strlcpy(user, un, sizeof(user));
739
		else
740
			user[0] = '\0';
741
	} else { /* ~user/ or ~user */
742
		memcpy(user, &fn[1], ulen);
743
		user[ulen] = '\0';
744
	}
745
	pw = getpwnam(user);
746
	if (pw != NULL) {
747
		plen = strlcpy(path, pw->pw_dir, sizeof(path));
748
		if (plen == 0 || path[plen - 1] != '/') {
749
			if (strlcat(path, "/", sizeof(path)) >= sizeof(path)) {
750
				dobeep();
751
				ewprintf("Path too long");
752
				return (NULL);
753
			}
754
		}
755
		fn = cp;
756
		if (*fn == '/')
757
			fn++;
758
	}
759
	if (strlcat(path, fn, sizeof(path)) >= sizeof(path)) {
760
		dobeep();
761
		ewprintf("Path too long");
762
		return (NULL);
763
	}
764
	if ((ret = strndup(path, NFILEN)) == NULL)
765
		return (NULL);
766
767
	return (ret);
768
}