"Fossies" - the Fresh Open Source Software Archive

Member "gawk-5.1.0/extension/filefuncs.c" (20 Mar 2020, 23629 Bytes) of package /linux/misc/gawk-5.1.0.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "filefuncs.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.0.1_vs_5.1.0.

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