(root)/
gawk-5.2.2/
extension/
filefuncs.c
       1  /*
       2   * filefuncs.c - Builtin functions that provide initial minimal interface
       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, 2023,
      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
     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 *
     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 *
     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 *
     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
     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
     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
     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
     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 *
     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 *
     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
     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 *
     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
     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
     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
     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
     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
     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 *
     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, "")