1 /*
2  * filefuncs.c - Builtin functions that provide initial minimal iterface
3  *		 to the file system.
4  *
5  * Arnold Robbins, update for 3.1, Mon Nov 23 12:53:39 EST 1998
6  * Arnold Robbins and John Haque, update for 3.1.4, applied Mon Jun 14 13:55:30 IDT 2004
7  * Arnold Robbins and Andrew Schorr, revised for new extension API, May 2012.
8  * Arnold Robbins, add fts(), August 2012
9  * Arnold Robbins, add statvfs(), November 2015
10  */
11 
12 /*
13  * Copyright (C) 2001, 2004, 2005, 2010-2021,
14  * the Free Software Foundation, Inc.
15  *
16  * This file is part of GAWK, the GNU implementation of the
17  * AWK Programming Language.
18  *
19  * GAWK is free software; you can redistribute it and/or modify
20  * it under the terms of the GNU General Public License as published by
21  * the Free Software Foundation; either version 3 of the License, or
22  * (at your option) any later version.
23  *
24  * GAWK is distributed in the hope that it will be useful,
25  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27  * GNU General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License
30  * along with this program; if not, write to the Free Software
31  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
32  */
33 
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
37 
38 #define _BSD_SOURCE
39 
40 #ifdef __VMS
41 #if (__CRTL_VER >= 70200000) && !defined (__VAX)
42 #define _LARGEFILE 1
43 #endif
44 
45 #ifndef __VAX
46 #ifdef __CRTL_VER
47 #if __CRTL_VER >= 80200000
48 #define _USE_STD_STAT 1
49 #endif
50 #endif
51 #endif
52 #define _POSIX_C_SOURCE 1
53 #define _XOPEN_SOURCE 1
54 #include <stat.h>
55 #ifndef S_ISVTX
56 #define S_ISVTX (0)
57 #endif
58 #ifndef major
59 #define major(s) (s)
60 #endif
61 #ifndef minor
62 #define minor(s) (0)
63 #endif
64 #include <unixlib.h>
65 #endif
66 
67 
68 #include <stdio.h>
69 #include <assert.h>
70 #include <errno.h>
71 #include <stdlib.h>
72 #include <string.h>
73 #include <unistd.h>
74 
75 #ifdef HAVE_SYS_PARAM_H
76 #include <sys/param.h>
77 #endif /* HAVE_SYS_PARAM_H */
78 
79 #if HAVE_SYS_SYSMACROS_H
80 #include <sys/sysmacros.h>
81 #elif HAVE_SYS_MKDEV_H
82 #include <sys/mkdev.h>
83 #endif /* HAVE_SYS_MKDEV_H */
84 
85 #include <sys/types.h>
86 
87 #include <sys/stat.h>
88 
89 #if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
90 #include <sys/statvfs.h>
91 #endif
92 
93 #include "gawkapi.h"
94 
95 #include "gettext.h"
96 #define _(msgid)  gettext(msgid)
97 #define N_(msgid) msgid
98 
99 #include "gawkfts.h"
100 #include "stack.h"
101 
102 #ifndef S_IFLNK
103 #define lstat stat
104 #define S_ISLNK(s) 0
105 #define readlink(f,b,bs) (-1)
106 #endif
107 
108 #ifdef __MINGW32__
109 #define S_IRGRP S_IRUSR
110 #define S_IWGRP S_IWUSR
111 #define S_IXGRP S_IXUSR
112 #define S_IROTH S_IRUSR
113 #define S_IWOTH S_IWUSR
114 #define S_IXOTH S_IXUSR
115 #define S_ISUID 0
116 #define S_ISGID 0
117 #define S_ISVTX 0
118 #define major(s) (s)
119 #define minor(s) (0)
120 
121 #define WIN32_LEAN_AND_MEAN
122 #include <windows.h>
123 
124 /* get_inode --- get the inode of a file */
125 static long long
get_inode(const char * fname)126 get_inode(const char *fname)
127 {
128 	HANDLE fh;
129 	BOOL ok;
130 	BY_HANDLE_FILE_INFORMATION info;
131 
132 	fh = CreateFile(fname, 0, 0, NULL, OPEN_EXISTING,
133 			FILE_FLAG_BACKUP_SEMANTICS, NULL);
134 	if (fh == INVALID_HANDLE_VALUE)
135 		return 0;
136 	ok = GetFileInformationByHandle(fh, &info);
137 	CloseHandle(fh);
138 	if (ok) {
139 		long long inode = info.nFileIndexHigh;
140 
141 		inode <<= 32;
142 		inode += info.nFileIndexLow;
143 		return inode;
144 	}
145 	return 0;
146 }
147 #endif
148 
149 static const gawk_api_t *api;	/* for convenience macros to work */
150 static awk_ext_id_t ext_id;
151 static awk_bool_t init_filefuncs(void);
152 static awk_bool_t (*init_func)(void) = init_filefuncs;
153 static const char *ext_version = "filefuncs extension: version 1.0";
154 
155 int plugin_is_GPL_compatible;
156 
157 /*  do_chdir --- provide dynamically loaded chdir() function for gawk */
158 
159 static awk_value_t *
do_chdir(int nargs,awk_value_t * result,struct awk_ext_func * unused)160 do_chdir(int nargs, awk_value_t *result, struct awk_ext_func *unused)
161 {
162 	awk_value_t newdir;
163 	int ret = -1;
164 
165 	assert(result != NULL);
166 
167 	if (get_argument(0, AWK_STRING, & newdir)) {
168 		ret = chdir(newdir.str_value.str);
169 		if (ret < 0)
170 			update_ERRNO_int(errno);
171 	}
172 
173 	return make_number(ret, result);
174 }
175 
176 /* format_mode --- turn a stat mode field into something readable */
177 
178 static char *
format_mode(unsigned long fmode)179 format_mode(unsigned long fmode)
180 {
181 	static char outbuf[12];
182 	static struct ftype_map {
183 		unsigned int mask;
184 		int charval;
185 	} ftype_map[] = {
186 		{ S_IFREG, '-' },	/* redundant */
187 		{ S_IFBLK, 'b' },
188 		{ S_IFCHR, 'c' },
189 		{ S_IFDIR, 'd' },
190 #ifdef S_IFSOCK
191 		{ S_IFSOCK, 's' },
192 #endif
193 #ifdef S_IFIFO
194 		{ S_IFIFO, 'p' },
195 #endif
196 #ifdef S_IFLNK
197 		{ S_IFLNK, 'l' },
198 #endif
199 #ifdef S_IFDOOR	/* Solaris weirdness */
200 		{ S_IFDOOR, 'D' },
201 #endif /* S_IFDOOR */
202 	};
203 	static struct mode_map {
204 		unsigned int mask;
205 		int rep;
206 	} map[] = {
207 		{ S_IRUSR, 'r' }, { S_IWUSR, 'w' }, { S_IXUSR, 'x' },
208 		{ S_IRGRP, 'r' }, { S_IWGRP, 'w' }, { S_IXGRP, 'x' },
209 		{ S_IROTH, 'r' }, { S_IWOTH, 'w' }, { S_IXOTH, 'x' },
210 	};
211 	static struct setuid_map {
212 		unsigned int mask;
213 		int index;
214 		int small_rep;
215 		int big_rep;
216 	} setuid_map[] = {
217 		{ S_ISUID, 3, 's', 'S' }, /* setuid bit */
218 		{ S_ISGID, 6, 's', 'l' }, /* setgid without execute == locking */
219 		{ S_ISVTX, 9, 't', 'T' }, /* the so-called "sticky" bit */
220 	};
221 	int i, j, k;
222 
223 	strcpy(outbuf, "----------");
224 
225 	/* first, get the file type */
226 	i = 0;
227 	for (j = 0, k = sizeof(ftype_map)/sizeof(ftype_map[0]); j < k; j++) {
228 		if ((fmode & S_IFMT) == ftype_map[j].mask) {
229 			outbuf[i] = ftype_map[j].charval;
230 			break;
231 		}
232 	}
233 
234 	/* now the permissions */
235 	for (j = 0, k = sizeof(map)/sizeof(map[0]); j < k; j++) {
236 		i++;
237 		if ((fmode & map[j].mask) != 0)
238 			outbuf[i] = map[j].rep;
239 	}
240 
241 	i++;
242 	outbuf[i] = '\0';
243 
244 	/* tweaks for the setuid / setgid / sticky bits */
245 	for (j = 0, k = sizeof(setuid_map)/sizeof(setuid_map[0]); j < k; j++) {
246 		if (fmode & setuid_map[j].mask) {
247 			if (outbuf[setuid_map[j].index] == 'x')
248 				outbuf[setuid_map[j].index] = setuid_map[j].small_rep;
249 			else
250 				outbuf[setuid_map[j].index] = setuid_map[j].big_rep;
251 		}
252 	}
253 
254 	return outbuf;
255 }
256 
257 /* read_symlink --- read a symbolic link into an allocated buffer.
258    This is based on xreadlink; the basic problem is that lstat cannot be relied
259    upon to return the proper size for a symbolic link.  This happens,
260    for example, on GNU/Linux in the /proc filesystem, where the symbolic link
261    sizes are often 0. */
262 
263 #ifndef SIZE_MAX
264 # define SIZE_MAX ((size_t) -1)
265 #endif
266 #ifndef SSIZE_MAX
267 # define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
268 #endif
269 
270 #define MAXSIZE (SIZE_MAX < SSIZE_MAX ? SIZE_MAX : SSIZE_MAX)
271 
272 static char *
read_symlink(const char * fname,size_t bufsize,ssize_t * linksize)273 read_symlink(const char *fname, size_t bufsize, ssize_t *linksize)
274 {
275 	if (bufsize)
276 		bufsize += 2;
277 	else
278 		bufsize = BUFSIZ * 2;
279 
280 	/* Make sure that bufsize >= 2 and within range */
281 	if (bufsize > MAXSIZE || bufsize < 2)
282 		bufsize = MAXSIZE;
283 
284 	while (1) {
285 		char *buf;
286 
287 		emalloc(buf, char *, bufsize, "read_symlink");
288 		if ((*linksize = readlink(fname, buf, bufsize)) < 0) {
289 			/* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink
290 			   returns -1 with errno == ERANGE if the buffer is
291 			   too small.  */
292 			if (errno != ERANGE) {
293 				gawk_free(buf);
294 				return NULL;
295 			}
296 		}
297 		/* N.B. This test is safe because bufsize must be >= 2 */
298 		else if ((size_t)*linksize <= bufsize-2) {
299 			buf[*linksize] = '\0';
300 			return buf;
301 		}
302 		gawk_free(buf);
303 		if (bufsize <= MAXSIZE/2)
304 			bufsize *= 2;
305 		else if (bufsize < MAXSIZE)
306 			bufsize = MAXSIZE;
307 		else
308 			return NULL;
309 	}
310 	return NULL;
311 }
312 
313 
314 /* device_blocksize --- try to figure out units of st_blocks */
315 
316 static int
device_blocksize()317 device_blocksize()
318 {
319 	/* some of this derived from GNULIB stat-size.h */
320 #if defined(DEV_BSIZE)
321 	/* <sys/param.h>, most systems */
322 	return DEV_BSIZE;
323 #elif defined(S_BLKSIZE)
324 	/* <sys/stat.h>, BSD systems */
325 	return S_BLKSIZE;
326 #elif defined hpux || defined __hpux__ || defined __hpux
327 	return 1024;
328 #elif defined _AIX && defined _I386
329 	/* AIX PS/2 counts st_blocks in 4K units.  */
330 	return 4 * 1024;
331 #elif defined __MINGW32__
332 	return 1024;
333 #else
334 	return 512;
335 #endif
336 }
337 
338 /* array_set --- set an array element */
339 
340 static void
array_set(awk_array_t array,const char * sub,awk_value_t * value)341 array_set(awk_array_t array, const char *sub, awk_value_t *value)
342 {
343 	awk_value_t index;
344 
345 	set_array_element(array,
346 			make_const_string(sub, strlen(sub), & index),
347 			value);
348 
349 }
350 
351 /* array_set_numeric --- set an array element with a number */
352 
353 static void
array_set_numeric(awk_array_t array,const char * sub,double num)354 array_set_numeric(awk_array_t array, const char *sub, double num)
355 {
356 	awk_value_t tmp;
357 
358 	array_set(array, sub, make_number(num, & tmp));
359 }
360 
361 /* fill_stat_array --- do the work to fill an array with stat info */
362 
363 static int
fill_stat_array(const char * name,awk_array_t array,struct stat * sbuf)364 fill_stat_array(const char *name, awk_array_t array, struct stat *sbuf)
365 {
366 	char *pmode;	/* printable mode */
367 	const char *type = "unknown";
368 	awk_value_t tmp;
369 	static struct ftype_map {
370 		unsigned int mask;
371 		const char *type;
372 	} ftype_map[] = {
373 		{ S_IFREG, "file" },
374 		{ S_IFBLK, "blockdev" },
375 		{ S_IFCHR, "chardev" },
376 		{ S_IFDIR, "directory" },
377 #ifdef S_IFSOCK
378 		{ S_IFSOCK, "socket" },
379 #endif
380 #ifdef S_IFIFO
381 		{ S_IFIFO, "fifo" },
382 #endif
383 #ifdef S_IFLNK
384 		{ S_IFLNK, "symlink" },
385 #endif
386 #ifdef S_IFDOOR	/* Solaris weirdness */
387 		{ S_IFDOOR, "door" },
388 #endif /* S_IFDOOR */
389 	};
390 	int j, k;
391 
392 	/* empty out the array */
393 	clear_array(array);
394 
395 	/* fill in the array */
396 	array_set(array, "name", make_const_string(name, strlen(name), & tmp));
397 	array_set_numeric(array, "dev", sbuf->st_dev);
398 #ifdef __MINGW32__
399 	array_set_numeric(array, "ino", (double)get_inode (name));
400 #else
401 	array_set_numeric(array, "ino", sbuf->st_ino);
402 #endif
403 	array_set_numeric(array, "mode", sbuf->st_mode);
404 	array_set_numeric(array, "nlink", sbuf->st_nlink);
405 	array_set_numeric(array, "uid", sbuf->st_uid);
406 	array_set_numeric(array, "gid", sbuf->st_gid);
407 	array_set_numeric(array, "size", sbuf->st_size);
408 #ifdef __MINGW32__
409 	array_set_numeric(array, "blocks", (double)((sbuf->st_size +
410 		device_blocksize() - 1) / device_blocksize()));
411 #else
412 	array_set_numeric(array, "blocks", sbuf->st_blocks);
413 #endif
414 	array_set_numeric(array, "atime", sbuf->st_atime);
415 	array_set_numeric(array, "mtime", sbuf->st_mtime);
416 	array_set_numeric(array, "ctime", sbuf->st_ctime);
417 
418 	/* for block and character devices, add rdev, major and minor numbers */
419 	if (S_ISBLK(sbuf->st_mode) || S_ISCHR(sbuf->st_mode)) {
420 		array_set_numeric(array, "rdev", sbuf->st_rdev);
421 		array_set_numeric(array, "major", major(sbuf->st_rdev));
422 		array_set_numeric(array, "minor", minor(sbuf->st_rdev));
423 	}
424 
425 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
426 	array_set_numeric(array, "blksize", sbuf->st_blksize);
427 #elif defined(__MINGW32__)
428 	array_set_numeric(array, "blksize", 4096);
429 #endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
430 
431 	/* the size of a block for st_blocks */
432 	array_set_numeric(array, "devbsize", device_blocksize());
433 
434 	pmode = format_mode(sbuf->st_mode);
435 	array_set(array, "pmode", make_const_string(pmode, strlen(pmode), & tmp));
436 
437 	/* for symbolic links, add a linkval field */
438 	if (S_ISLNK(sbuf->st_mode)) {
439 		char *buf;
440 		ssize_t linksize;
441 
442 		if ((buf = read_symlink(name, sbuf->st_size,
443 					& linksize)) != NULL)
444 			array_set(array, "linkval", make_malloced_string(buf, linksize, & tmp));
445 		else
446 			warning(ext_id, _("stat: unable to read symbolic link `%s'"), name);
447 	}
448 
449 	/* add a type field */
450 	type = "unknown";	/* shouldn't happen */
451 	for (j = 0, k = sizeof(ftype_map)/sizeof(ftype_map[0]); j < k; j++) {
452 		if ((sbuf->st_mode & S_IFMT) == ftype_map[j].mask) {
453 			type = ftype_map[j].type;
454 			break;
455 		}
456 	}
457 
458 	array_set(array, "type", make_const_string(type, strlen(type), & tmp));
459 
460 	return 0;
461 }
462 
463 /* do_stat --- provide a stat() function for gawk */
464 
465 static awk_value_t *
do_stat(int nargs,awk_value_t * result,struct awk_ext_func * unused)466 do_stat(int nargs, awk_value_t *result, struct awk_ext_func *unused)
467 {
468 	awk_value_t file_param, array_param;
469 	char *name;
470 	awk_array_t array;
471 	int ret;
472 	struct stat sbuf;
473 	int (*statfunc)(const char *path, struct stat *sbuf) = lstat;	/* default */
474 
475 	assert(result != NULL);
476 
477 	/* file is first arg, array to hold results is second */
478 	if (! get_argument(0, AWK_STRING, & file_param)) {
479 		warning(ext_id, _("stat: first argument is not a string"));
480 		return make_number(-1, result);
481 	}
482 
483 	if (! get_argument(1, AWK_ARRAY, & array_param)) {
484 		warning(ext_id, _("stat: second argument is not an array"));
485 		return make_number(-1, result);
486 	}
487 
488 	if (nargs == 3) {
489 		statfunc = stat;
490 	}
491 
492 	name = file_param.str_value.str;
493 	array = array_param.array_cookie;
494 
495 	/* always empty out the array */
496 	clear_array(array);
497 
498 	/* stat the file; if error, set ERRNO and return */
499 	ret = statfunc(name, & sbuf);
500 	if (ret < 0) {
501 		update_ERRNO_int(errno);
502 		return make_number(ret, result);
503 	}
504 
505 	ret = fill_stat_array(name, array, & sbuf);
506 
507 	return make_number(ret, result);
508 }
509 
510 #if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
511 
512 /* do_statvfs --- provide a statvfs() function for gawk */
513 
514 static awk_value_t *
do_statvfs(int nargs,awk_value_t * result,struct awk_ext_func * unused)515 do_statvfs(int nargs, awk_value_t *result, struct awk_ext_func *unused)
516 {
517 	awk_value_t file_param, array_param;
518 	char *name;
519 	awk_array_t array;
520 	int ret;
521 	struct statvfs vfsbuf;
522 
523 	assert(result != NULL);
524 
525 	/* file is first arg, array to hold results is second */
526 	if (   ! get_argument(0, AWK_STRING, & file_param)
527 	    || ! get_argument(1, AWK_ARRAY, & array_param)) {
528 		warning(ext_id, _("stat: bad parameters"));
529 		return make_number(-1, result);
530 	}
531 
532 	name = file_param.str_value.str;
533 	array = array_param.array_cookie;
534 
535 	/* always empty out the array */
536 	clear_array(array);
537 
538 	/* statvfs the filesystem; if error, set ERRNO and return */
539 	ret = statvfs(name, & vfsbuf);
540 	if (ret < 0) {
541 		update_ERRNO_int(errno);
542 		return make_number(ret, result);
543 	}
544 
545 	array_set_numeric(array, "bsize", vfsbuf.f_bsize);	/* filesystem block size */
546 	array_set_numeric(array, "frsize", vfsbuf.f_frsize);	/* fragment size */
547 	array_set_numeric(array, "blocks", vfsbuf.f_blocks);	/* size of fs in f_frsize units */
548 	array_set_numeric(array, "bfree", vfsbuf.f_bfree);	/* # free blocks */
549 	array_set_numeric(array, "bavail", vfsbuf.f_bavail);	/* # free blocks for unprivileged users */
550 	array_set_numeric(array, "files", vfsbuf.f_files);	/* # inodes */
551 	array_set_numeric(array, "ffree", vfsbuf.f_ffree);	/* # free inodes */
552 	array_set_numeric(array, "favail", vfsbuf.f_favail);	/* # free inodes for unprivileged users */
553 #ifndef _AIX
554 	array_set_numeric(array, "fsid", vfsbuf.f_fsid);	/* filesystem ID */
555 #endif
556 	array_set_numeric(array, "flag", vfsbuf.f_flag);	/* mount flags */
557 	array_set_numeric(array, "namemax", vfsbuf.f_namemax);	/* maximum filename length */
558 
559 
560 	return make_number(ret, result);
561 }
562 #endif
563 
564 /* init_filefuncs --- initialization routine */
565 
566 static awk_bool_t
init_filefuncs(void)567 init_filefuncs(void)
568 {
569 	int errors = 0;
570 	int i;
571 	awk_value_t value;
572 
573 #ifndef __MINGW32__
574 	/* at least right now, only FTS needs initializing */
575 #define FTS_NON_RECURSIVE	FTS_STOP	/* Don't step into directories.  */
576 	static struct flagtab {
577 		const char *name;
578 		int value;
579 	} opentab[] = {
580 #define ENTRY(x)	{ #x, x }
581 		ENTRY(FTS_COMFOLLOW),
582 		ENTRY(FTS_LOGICAL),
583 		ENTRY(FTS_NOCHDIR),
584 		ENTRY(FTS_PHYSICAL),
585 		ENTRY(FTS_SEEDOT),
586 		ENTRY(FTS_XDEV),
587 		{"FTS_SKIP", FTS_NON_RECURSIVE},
588 		{ NULL, 0 }
589 	};
590 
591 	for (i = 0; opentab[i].name != NULL; i++) {
592 		(void) make_number(opentab[i].value, & value);
593 		if (! sym_update(opentab[i].name, & value)) {
594 			warning(ext_id, _("fts init: could not create variable %s"),
595 					opentab[i].name);
596 			errors++;
597 		}
598 	}
599 #endif
600 	return errors == 0;
601 }
602 
603 #ifdef __MINGW32__
604 /*  do_fts --- walk a hierarchy and fill in an array */
605 
606 /*
607  * Usage from awk:
608  *	flags = or(FTS_PHYSICAL, ...)
609  *	result = fts(pathlist, flags, filedata)
610  */
611 
612 static awk_value_t *
do_fts(int nargs,awk_value_t * result,struct awk_ext_func * unused)613 do_fts(int nargs, awk_value_t *result, struct awk_ext_func *unused)
614 {
615 	fatal(ext_id, _("fts is not supported on this system"));
616 
617 	return NULL;	/* for the compiler */
618 }
619 
620 #else /* __MINGW32__ */
621 
622 static int fts_errors = 0;
623 
624 /* fill_stat_element --- fill in stat element of array */
625 
626 static void
fill_stat_element(awk_array_t element_array,const char * name,struct stat * sbuf)627 fill_stat_element(awk_array_t element_array, const char *name, struct stat *sbuf)
628 {
629 	awk_value_t index, value;
630 	awk_array_t stat_array;
631 
632 	stat_array = create_array();
633 	if (stat_array == NULL) {
634 		warning(ext_id, _("fill_stat_element: could not create array, out of memory"));
635 		fts_errors++;
636 		return;
637 	}
638 	fill_stat_array(name, stat_array, sbuf);
639 	(void) make_const_string("stat", 4, & index);
640 	value.val_type = AWK_ARRAY;
641 	value.array_cookie = stat_array;
642 	if (! set_array_element(element_array, & index, & value)) {
643 		warning(ext_id, _("fill_stat_element: could not set element"));
644 		fts_errors++;
645 	}
646 }
647 
648 /* fill_path_element --- fill in path element of array */
649 
650 static void
fill_path_element(awk_array_t element_array,const char * path)651 fill_path_element(awk_array_t element_array, const char *path)
652 {
653 	awk_value_t index, value;
654 
655 	(void) make_const_string("path", 4, & index);
656 	(void) make_const_string(path, strlen(path), & value);
657 	if (! set_array_element(element_array, & index, & value)) {
658 		warning(ext_id, _("fill_path_element: could not set element"));
659 		fts_errors++;
660 	}
661 }
662 
663 /* fill_error_element --- fill in error element of array */
664 
665 static void
fill_error_element(awk_array_t element_array,const int errcode)666 fill_error_element(awk_array_t element_array, const int errcode)
667 {
668 	awk_value_t index, value;
669 	const char *err = strerror(errcode);
670 
671 	(void) make_const_string("error", 5, & index);
672 	(void) make_const_string(err, strlen(err), & value);
673 	if (! set_array_element(element_array, & index, & value)) {
674 		warning(ext_id, _("fill_error_element: could not set element"));
675 		fts_errors++;
676 	}
677 }
678 
679 /* fill_default_elements --- fill in stat and path elements */
680 
681 static void
fill_default_elements(awk_array_t element_array,const FTSENT * const fentry,awk_bool_t bad_ret)682 fill_default_elements(awk_array_t element_array, const FTSENT *const fentry, awk_bool_t bad_ret)
683 {
684 	/* full path */
685 	fill_path_element(element_array, fentry->fts_path);
686 
687 	/* stat info */
688 	if (! bad_ret) {
689 		fill_stat_element(element_array,
690 				fentry->fts_name,
691 				fentry->fts_statp);
692 	}
693 
694 	/* error info */
695 	if (bad_ret || fentry->fts_errno != 0) {
696 		fill_error_element(element_array, fentry->fts_errno);
697 	}
698 }
699 
700 /* process --- process the hierarchy */
701 
702 static void
process(FTS * hierarchy,awk_array_t destarray,int seedot,int skipset)703 process(FTS *hierarchy, awk_array_t destarray, int seedot, int skipset)
704 {
705 	FTSENT *fentry;
706 	awk_value_t index, value;
707 	awk_array_t element_array, newdir_array, dot_array;
708 	awk_bool_t bad_ret = awk_false;
709 
710 	/* path is full path,  pathlen is length thereof */
711 	/* name is name in directory, namelen is length thereof */
712 	while ((fentry = fts_read(hierarchy)) != NULL) {
713 		bad_ret = awk_false;
714 
715 		switch (fentry->fts_info) {
716 		case FTS_D:
717 			/* directory */
718 
719 			if (skipset && fentry->fts_level == 0)
720 				fts_set(hierarchy, fentry, FTS_SKIP);
721 
722 			/* create array to hold entries */
723 			/* this will be empty if doing FTS_SKIP */
724 			newdir_array = create_array();
725 			if (newdir_array == NULL) {
726 				warning(ext_id, _("fts-process: could not create array"));
727 				fts_errors++;
728 				break;
729 			}
730 
731 			/* store new directory in its parent directory */
732 			(void) make_const_string(fentry->fts_name, fentry->fts_namelen, & index);
733 			value.val_type = AWK_ARRAY;
734 			value.array_cookie = newdir_array;
735 			if (! set_array_element(destarray, & index, & value)) {
736 				warning(ext_id, _("fts-process: could not set element"));
737 				fts_errors++;
738 				break;
739 			}
740 			newdir_array = value.array_cookie;
741 
742 			/* push current directory */
743 			stack_push(destarray);
744 
745 			/* new directory becomes current */
746 			destarray = newdir_array;
747 			break;
748 
749 		case FTS_DNR:
750 		case FTS_DC:
751 		case FTS_ERR:
752 		case FTS_NS:
753 			/* error */
754 			bad_ret = awk_true;
755 			/* fall through */
756 
757 		case FTS_NSOK:
758 		case FTS_SL:
759 		case FTS_SLNONE:
760 		case FTS_F:
761 		case FTS_DOT:
762 			/* if see dot, skip "." */
763 			if (seedot && strcmp(fentry->fts_name, ".") == 0)
764 				break;
765 
766 			/*
767 			 * File case.
768 			 * destarray is the directory we're reading.
769 			 * step 1: create new empty array
770 			 */
771 			element_array = create_array();
772 			if (element_array == NULL) {
773 				warning(ext_id, _("fts-process: could not create array"));
774 				fts_errors++;
775 				break;
776 			}
777 
778 			/* step 2: add element array to parent array */
779 			(void) make_const_string(fentry->fts_name, fentry->fts_namelen, & index);
780 			value.val_type = AWK_ARRAY;
781 			value.array_cookie = element_array;
782 			if (! set_array_element(destarray, & index, & value)) {
783 				warning(ext_id, _("fts-process: could not set element"));
784 				fts_errors++;
785 				break;
786 			}
787 
788 			/* step 3: fill in path, stat, error elements */
789 			fill_default_elements(element_array, fentry, bad_ret);
790 			break;
791 
792 		case FTS_DP:
793 			/* create "." subarray */
794 			dot_array = create_array();
795 
796 			/* add it to parent */
797 			(void) make_const_string(".", 1, & index);
798 			value.val_type = AWK_ARRAY;
799 			value.array_cookie = dot_array;
800 			if (! set_array_element(destarray, & index, & value)) {
801 				warning(ext_id, _("fts-process: could not set element"));
802 				fts_errors++;
803 				break;
804 			}
805 
806 			/* fill it in with path, stat, error elements */
807 			fill_default_elements(dot_array, fentry, bad_ret);
808 
809 			/* now pop the parent directory off the stack */
810 			if (! stack_empty()) {
811 				/* pop stack */
812 				destarray = stack_pop();
813 			}
814 
815 			break;
816 
817 		case FTS_DEFAULT:
818 			/* nothing to do */
819 			break;
820 		}
821 	}
822 }
823 
824 /*  do_fts --- walk a hierarchy and fill in an array */
825 
826 /*
827  * Usage from awk:
828  *	flags = or(FTS_PHYSICAL, ...)
829  *	result = fts(pathlist, flags, filedata)
830  */
831 
832 static awk_value_t *
do_fts(int nargs,awk_value_t * result,struct awk_ext_func * unused)833 do_fts(int nargs, awk_value_t *result, struct awk_ext_func *unused)
834 {
835 	awk_value_t pathlist, flagval, dest;
836 	awk_flat_array_t *path_array = NULL;
837 	char **pathvector = NULL;
838 	FTS *hierarchy;
839 	int flags;
840 	size_t i, count;
841 	int ret = -1;
842 	static const int mask = (
843 		  FTS_COMFOLLOW | FTS_LOGICAL | FTS_NOCHDIR | FTS_PHYSICAL
844 		| FTS_SEEDOT | FTS_XDEV | FTS_NON_RECURSIVE);
845 
846 	assert(result != NULL);
847 	fts_errors = 0;		/* ensure a fresh start */
848 
849 	if (nargs > 3)
850 		lintwarn(ext_id, _("fts: called with incorrect number of arguments, expecting 3"));
851 
852 	if (! get_argument(0, AWK_ARRAY, & pathlist)) {
853 		warning(ext_id, _("fts: first argument is not an array"));
854 		update_ERRNO_int(EINVAL);
855 		goto out;
856 	}
857 
858 	if (! get_argument(1, AWK_NUMBER, & flagval)) {
859 		warning(ext_id, _("fts: second argument is not a number"));
860 		update_ERRNO_int(EINVAL);
861 		goto out;
862 	}
863 
864 	if (! get_argument(2, AWK_ARRAY, & dest)) {
865 		warning(ext_id, _("fts: third argument is not an array"));
866 		update_ERRNO_int(EINVAL);
867 		goto out;
868 	}
869 
870 	/* flatten pathlist */
871 	if (! flatten_array(pathlist.array_cookie, & path_array)) {
872 		warning(ext_id, _("fts: could not flatten array\n"));
873 		goto out;
874 	}
875 
876 	/* check the flags first, before the array flattening */
877 
878 	/* get flags */
879 	flags = flagval.num_value;
880 
881 	/* enforce physical or logical but not both, and not no_stat */
882 	if ((flags & (FTS_PHYSICAL|FTS_LOGICAL)) == 0
883 	    || (flags & (FTS_PHYSICAL|FTS_LOGICAL)) == (FTS_PHYSICAL|FTS_LOGICAL)) {
884 		update_ERRNO_int(EINVAL);
885 		goto out;
886 	}
887 	if ((flags & FTS_NOSTAT) != 0) {
888 		flags &= ~FTS_NOSTAT;
889 		if (do_lint)
890 			lintwarn(ext_id, _("fts: ignoring sneaky FTS_NOSTAT flag. nyah, nyah, nyah."));
891 	}
892 	flags &= mask;	/* turn off anything else */
893 
894 	if (flags & FTS_NON_RECURSIVE)
895 		flags |= FTS_NOCHDIR;
896 
897 	/* make pathvector */
898 	count = path_array->count + 1;
899 	ezalloc(pathvector, char **, count * sizeof(char *), "do_fts");
900 
901 	/* fill it in */
902 	count--;	/* ignore final NULL at end of vector */
903 	for (i = 0; i < count; i++)
904 		pathvector[i] = path_array->elements[i].value.str_value.str;
905 
906 
907 	/* clear dest array */
908 	assert(clear_array(dest.array_cookie));
909 
910 	/* let's do it! */
911 	hierarchy = fts_open(pathvector, flags & ~FTS_NON_RECURSIVE, NULL);
912 	if (hierarchy != NULL) {
913 		process(hierarchy, dest.array_cookie,
914 			(flags & FTS_SEEDOT) != 0,
915 			(flags & FTS_NON_RECURSIVE) != 0);
916 		fts_close(hierarchy);
917 
918 		if (fts_errors == 0)
919 			ret = 0;
920 	} else
921 		update_ERRNO_int(errno);
922 
923 out:
924 	if (pathvector != NULL)
925 		gawk_free(pathvector);
926 	if (path_array != NULL)
927 		(void) release_flattened_array(pathlist.array_cookie, path_array);
928 
929 	return make_number(ret, result);
930 }
931 #endif	/* ! __MINGW32__ */
932 
933 static awk_ext_func_t func_table[] = {
934 	{ "chdir",	do_chdir, 1, 1, awk_false, NULL },
935 	{ "stat",	do_stat, 3, 2, awk_false, NULL },
936 #ifndef __MINGW32__
937 	{ "fts",	do_fts, 3, 3, awk_false, NULL },
938 #endif
939 #if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
940 	{ "statvfs",	do_statvfs, 2, 2, awk_false, NULL },
941 #endif
942 };
943 
944 
945 /* define the dl_load function using the boilerplate macro */
946 
947 dl_load_func(func_table, filefuncs, "")
948