948 lines
23 KiB
C
948 lines
23 KiB
C
/*
|
|
* filefuncs.c - Builtin functions that provide initial minimal iterface
|
|
* to the file system.
|
|
*
|
|
* Arnold Robbins, update for 3.1, Mon Nov 23 12:53:39 EST 1998
|
|
* Arnold Robbins and John Haque, update for 3.1.4, applied Mon Jun 14 13:55:30 IDT 2004
|
|
* Arnold Robbins and Andrew Schorr, revised for new extension API, May 2012.
|
|
* Arnold Robbins, add fts(), August 2012
|
|
* Arnold Robbins, add statvfs(), November 2015
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2001, 2004, 2005, 2010-2021,
|
|
* the Free Software Foundation, Inc.
|
|
*
|
|
* This file is part of GAWK, the GNU implementation of the
|
|
* AWK Programming Language.
|
|
*
|
|
* GAWK is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GAWK is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#define _BSD_SOURCE
|
|
|
|
#ifdef __VMS
|
|
#if (__CRTL_VER >= 70200000) && !defined (__VAX)
|
|
#define _LARGEFILE 1
|
|
#endif
|
|
|
|
#ifndef __VAX
|
|
#ifdef __CRTL_VER
|
|
#if __CRTL_VER >= 80200000
|
|
#define _USE_STD_STAT 1
|
|
#endif
|
|
#endif
|
|
#endif
|
|
#define _POSIX_C_SOURCE 1
|
|
#define _XOPEN_SOURCE 1
|
|
#include <stat.h>
|
|
#ifndef S_ISVTX
|
|
#define S_ISVTX (0)
|
|
#endif
|
|
#ifndef major
|
|
#define major(s) (s)
|
|
#endif
|
|
#ifndef minor
|
|
#define minor(s) (0)
|
|
#endif
|
|
#include <unixlib.h>
|
|
#endif
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef HAVE_SYS_PARAM_H
|
|
#include <sys/param.h>
|
|
#endif /* HAVE_SYS_PARAM_H */
|
|
|
|
#if HAVE_SYS_SYSMACROS_H
|
|
#include <sys/sysmacros.h>
|
|
#elif HAVE_SYS_MKDEV_H
|
|
#include <sys/mkdev.h>
|
|
#endif /* HAVE_SYS_MKDEV_H */
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
|
|
#include <sys/statvfs.h>
|
|
#endif
|
|
|
|
#include "gawkapi.h"
|
|
|
|
#include "gettext.h"
|
|
#define _(msgid) gettext(msgid)
|
|
#define N_(msgid) msgid
|
|
|
|
#include "gawkfts.h"
|
|
#include "stack.h"
|
|
|
|
#ifndef S_IFLNK
|
|
#define lstat stat
|
|
#define S_ISLNK(s) 0
|
|
#define readlink(f,b,bs) (-1)
|
|
#endif
|
|
|
|
#ifdef __MINGW32__
|
|
#define S_IRGRP S_IRUSR
|
|
#define S_IWGRP S_IWUSR
|
|
#define S_IXGRP S_IXUSR
|
|
#define S_IROTH S_IRUSR
|
|
#define S_IWOTH S_IWUSR
|
|
#define S_IXOTH S_IXUSR
|
|
#define S_ISUID 0
|
|
#define S_ISGID 0
|
|
#define S_ISVTX 0
|
|
#define major(s) (s)
|
|
#define minor(s) (0)
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
|
|
/* get_inode --- get the inode of a file */
|
|
static long long
|
|
get_inode(const char *fname)
|
|
{
|
|
HANDLE fh;
|
|
BOOL ok;
|
|
BY_HANDLE_FILE_INFORMATION info;
|
|
|
|
fh = CreateFile(fname, 0, 0, NULL, OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
|
if (fh == INVALID_HANDLE_VALUE)
|
|
return 0;
|
|
ok = GetFileInformationByHandle(fh, &info);
|
|
CloseHandle(fh);
|
|
if (ok) {
|
|
long long inode = info.nFileIndexHigh;
|
|
|
|
inode <<= 32;
|
|
inode += info.nFileIndexLow;
|
|
return inode;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const gawk_api_t *api; /* for convenience macros to work */
|
|
static awk_ext_id_t ext_id;
|
|
static awk_bool_t init_filefuncs(void);
|
|
static awk_bool_t (*init_func)(void) = init_filefuncs;
|
|
static const char *ext_version = "filefuncs extension: version 1.0";
|
|
|
|
int plugin_is_GPL_compatible;
|
|
|
|
/* do_chdir --- provide dynamically loaded chdir() function for gawk */
|
|
|
|
static awk_value_t *
|
|
do_chdir(int nargs, awk_value_t *result, struct awk_ext_func *unused)
|
|
{
|
|
awk_value_t newdir;
|
|
int ret = -1;
|
|
|
|
assert(result != NULL);
|
|
|
|
if (get_argument(0, AWK_STRING, & newdir)) {
|
|
ret = chdir(newdir.str_value.str);
|
|
if (ret < 0)
|
|
update_ERRNO_int(errno);
|
|
}
|
|
|
|
return make_number(ret, result);
|
|
}
|
|
|
|
/* format_mode --- turn a stat mode field into something readable */
|
|
|
|
static char *
|
|
format_mode(unsigned long fmode)
|
|
{
|
|
static char outbuf[12];
|
|
static struct ftype_map {
|
|
unsigned int mask;
|
|
int charval;
|
|
} ftype_map[] = {
|
|
{ S_IFREG, '-' }, /* redundant */
|
|
{ S_IFBLK, 'b' },
|
|
{ S_IFCHR, 'c' },
|
|
{ S_IFDIR, 'd' },
|
|
#ifdef S_IFSOCK
|
|
{ S_IFSOCK, 's' },
|
|
#endif
|
|
#ifdef S_IFIFO
|
|
{ S_IFIFO, 'p' },
|
|
#endif
|
|
#ifdef S_IFLNK
|
|
{ S_IFLNK, 'l' },
|
|
#endif
|
|
#ifdef S_IFDOOR /* Solaris weirdness */
|
|
{ S_IFDOOR, 'D' },
|
|
#endif /* S_IFDOOR */
|
|
};
|
|
static struct mode_map {
|
|
unsigned int mask;
|
|
int rep;
|
|
} map[] = {
|
|
{ S_IRUSR, 'r' }, { S_IWUSR, 'w' }, { S_IXUSR, 'x' },
|
|
{ S_IRGRP, 'r' }, { S_IWGRP, 'w' }, { S_IXGRP, 'x' },
|
|
{ S_IROTH, 'r' }, { S_IWOTH, 'w' }, { S_IXOTH, 'x' },
|
|
};
|
|
static struct setuid_map {
|
|
unsigned int mask;
|
|
int index;
|
|
int small_rep;
|
|
int big_rep;
|
|
} setuid_map[] = {
|
|
{ S_ISUID, 3, 's', 'S' }, /* setuid bit */
|
|
{ S_ISGID, 6, 's', 'l' }, /* setgid without execute == locking */
|
|
{ S_ISVTX, 9, 't', 'T' }, /* the so-called "sticky" bit */
|
|
};
|
|
int i, j, k;
|
|
|
|
strcpy(outbuf, "----------");
|
|
|
|
/* first, get the file type */
|
|
i = 0;
|
|
for (j = 0, k = sizeof(ftype_map)/sizeof(ftype_map[0]); j < k; j++) {
|
|
if ((fmode & S_IFMT) == ftype_map[j].mask) {
|
|
outbuf[i] = ftype_map[j].charval;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* now the permissions */
|
|
for (j = 0, k = sizeof(map)/sizeof(map[0]); j < k; j++) {
|
|
i++;
|
|
if ((fmode & map[j].mask) != 0)
|
|
outbuf[i] = map[j].rep;
|
|
}
|
|
|
|
i++;
|
|
outbuf[i] = '\0';
|
|
|
|
/* tweaks for the setuid / setgid / sticky bits */
|
|
for (j = 0, k = sizeof(setuid_map)/sizeof(setuid_map[0]); j < k; j++) {
|
|
if (fmode & setuid_map[j].mask) {
|
|
if (outbuf[setuid_map[j].index] == 'x')
|
|
outbuf[setuid_map[j].index] = setuid_map[j].small_rep;
|
|
else
|
|
outbuf[setuid_map[j].index] = setuid_map[j].big_rep;
|
|
}
|
|
}
|
|
|
|
return outbuf;
|
|
}
|
|
|
|
/* read_symlink --- read a symbolic link into an allocated buffer.
|
|
This is based on xreadlink; the basic problem is that lstat cannot be relied
|
|
upon to return the proper size for a symbolic link. This happens,
|
|
for example, on GNU/Linux in the /proc filesystem, where the symbolic link
|
|
sizes are often 0. */
|
|
|
|
#ifndef SIZE_MAX
|
|
# define SIZE_MAX ((size_t) -1)
|
|
#endif
|
|
#ifndef SSIZE_MAX
|
|
# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
|
|
#endif
|
|
|
|
#define MAXSIZE (SIZE_MAX < SSIZE_MAX ? SIZE_MAX : SSIZE_MAX)
|
|
|
|
static char *
|
|
read_symlink(const char *fname, size_t bufsize, ssize_t *linksize)
|
|
{
|
|
if (bufsize)
|
|
bufsize += 2;
|
|
else
|
|
bufsize = BUFSIZ * 2;
|
|
|
|
/* Make sure that bufsize >= 2 and within range */
|
|
if (bufsize > MAXSIZE || bufsize < 2)
|
|
bufsize = MAXSIZE;
|
|
|
|
while (1) {
|
|
char *buf;
|
|
|
|
emalloc(buf, char *, bufsize, "read_symlink");
|
|
if ((*linksize = readlink(fname, buf, bufsize)) < 0) {
|
|
/* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink
|
|
returns -1 with errno == ERANGE if the buffer is
|
|
too small. */
|
|
if (errno != ERANGE) {
|
|
gawk_free(buf);
|
|
return NULL;
|
|
}
|
|
}
|
|
/* N.B. This test is safe because bufsize must be >= 2 */
|
|
else if ((size_t)*linksize <= bufsize-2) {
|
|
buf[*linksize] = '\0';
|
|
return buf;
|
|
}
|
|
gawk_free(buf);
|
|
if (bufsize <= MAXSIZE/2)
|
|
bufsize *= 2;
|
|
else if (bufsize < MAXSIZE)
|
|
bufsize = MAXSIZE;
|
|
else
|
|
return NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* device_blocksize --- try to figure out units of st_blocks */
|
|
|
|
static int
|
|
device_blocksize()
|
|
{
|
|
/* some of this derived from GNULIB stat-size.h */
|
|
#if defined(DEV_BSIZE)
|
|
/* <sys/param.h>, most systems */
|
|
return DEV_BSIZE;
|
|
#elif defined(S_BLKSIZE)
|
|
/* <sys/stat.h>, BSD systems */
|
|
return S_BLKSIZE;
|
|
#elif defined hpux || defined __hpux__ || defined __hpux
|
|
return 1024;
|
|
#elif defined _AIX && defined _I386
|
|
/* AIX PS/2 counts st_blocks in 4K units. */
|
|
return 4 * 1024;
|
|
#elif defined __MINGW32__
|
|
return 1024;
|
|
#else
|
|
return 512;
|
|
#endif
|
|
}
|
|
|
|
/* array_set --- set an array element */
|
|
|
|
static void
|
|
array_set(awk_array_t array, const char *sub, awk_value_t *value)
|
|
{
|
|
awk_value_t index;
|
|
|
|
set_array_element(array,
|
|
make_const_string(sub, strlen(sub), & index),
|
|
value);
|
|
|
|
}
|
|
|
|
/* array_set_numeric --- set an array element with a number */
|
|
|
|
static void
|
|
array_set_numeric(awk_array_t array, const char *sub, double num)
|
|
{
|
|
awk_value_t tmp;
|
|
|
|
array_set(array, sub, make_number(num, & tmp));
|
|
}
|
|
|
|
/* fill_stat_array --- do the work to fill an array with stat info */
|
|
|
|
static int
|
|
fill_stat_array(const char *name, awk_array_t array, struct stat *sbuf)
|
|
{
|
|
char *pmode; /* printable mode */
|
|
const char *type = "unknown";
|
|
awk_value_t tmp;
|
|
static struct ftype_map {
|
|
unsigned int mask;
|
|
const char *type;
|
|
} ftype_map[] = {
|
|
{ S_IFREG, "file" },
|
|
{ S_IFBLK, "blockdev" },
|
|
{ S_IFCHR, "chardev" },
|
|
{ S_IFDIR, "directory" },
|
|
#ifdef S_IFSOCK
|
|
{ S_IFSOCK, "socket" },
|
|
#endif
|
|
#ifdef S_IFIFO
|
|
{ S_IFIFO, "fifo" },
|
|
#endif
|
|
#ifdef S_IFLNK
|
|
{ S_IFLNK, "symlink" },
|
|
#endif
|
|
#ifdef S_IFDOOR /* Solaris weirdness */
|
|
{ S_IFDOOR, "door" },
|
|
#endif /* S_IFDOOR */
|
|
};
|
|
int j, k;
|
|
|
|
/* empty out the array */
|
|
clear_array(array);
|
|
|
|
/* fill in the array */
|
|
array_set(array, "name", make_const_string(name, strlen(name), & tmp));
|
|
array_set_numeric(array, "dev", sbuf->st_dev);
|
|
#ifdef __MINGW32__
|
|
array_set_numeric(array, "ino", (double)get_inode (name));
|
|
#else
|
|
array_set_numeric(array, "ino", sbuf->st_ino);
|
|
#endif
|
|
array_set_numeric(array, "mode", sbuf->st_mode);
|
|
array_set_numeric(array, "nlink", sbuf->st_nlink);
|
|
array_set_numeric(array, "uid", sbuf->st_uid);
|
|
array_set_numeric(array, "gid", sbuf->st_gid);
|
|
array_set_numeric(array, "size", sbuf->st_size);
|
|
#ifdef __MINGW32__
|
|
array_set_numeric(array, "blocks", (double)((sbuf->st_size +
|
|
device_blocksize() - 1) / device_blocksize()));
|
|
#else
|
|
array_set_numeric(array, "blocks", sbuf->st_blocks);
|
|
#endif
|
|
array_set_numeric(array, "atime", sbuf->st_atime);
|
|
array_set_numeric(array, "mtime", sbuf->st_mtime);
|
|
array_set_numeric(array, "ctime", sbuf->st_ctime);
|
|
|
|
/* for block and character devices, add rdev, major and minor numbers */
|
|
if (S_ISBLK(sbuf->st_mode) || S_ISCHR(sbuf->st_mode)) {
|
|
array_set_numeric(array, "rdev", sbuf->st_rdev);
|
|
array_set_numeric(array, "major", major(sbuf->st_rdev));
|
|
array_set_numeric(array, "minor", minor(sbuf->st_rdev));
|
|
}
|
|
|
|
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
|
|
array_set_numeric(array, "blksize", sbuf->st_blksize);
|
|
#elif defined(__MINGW32__)
|
|
array_set_numeric(array, "blksize", 4096);
|
|
#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
|
|
|
|
/* the size of a block for st_blocks */
|
|
array_set_numeric(array, "devbsize", device_blocksize());
|
|
|
|
pmode = format_mode(sbuf->st_mode);
|
|
array_set(array, "pmode", make_const_string(pmode, strlen(pmode), & tmp));
|
|
|
|
/* for symbolic links, add a linkval field */
|
|
if (S_ISLNK(sbuf->st_mode)) {
|
|
char *buf;
|
|
ssize_t linksize;
|
|
|
|
if ((buf = read_symlink(name, sbuf->st_size,
|
|
& linksize)) != NULL)
|
|
array_set(array, "linkval", make_malloced_string(buf, linksize, & tmp));
|
|
else
|
|
warning(ext_id, _("stat: unable to read symbolic link `%s'"), name);
|
|
}
|
|
|
|
/* add a type field */
|
|
type = "unknown"; /* shouldn't happen */
|
|
for (j = 0, k = sizeof(ftype_map)/sizeof(ftype_map[0]); j < k; j++) {
|
|
if ((sbuf->st_mode & S_IFMT) == ftype_map[j].mask) {
|
|
type = ftype_map[j].type;
|
|
break;
|
|
}
|
|
}
|
|
|
|
array_set(array, "type", make_const_string(type, strlen(type), & tmp));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* do_stat --- provide a stat() function for gawk */
|
|
|
|
static awk_value_t *
|
|
do_stat(int nargs, awk_value_t *result, struct awk_ext_func *unused)
|
|
{
|
|
awk_value_t file_param, array_param;
|
|
char *name;
|
|
awk_array_t array;
|
|
int ret;
|
|
struct stat sbuf;
|
|
int (*statfunc)(const char *path, struct stat *sbuf) = lstat; /* default */
|
|
|
|
assert(result != NULL);
|
|
|
|
/* file is first arg, array to hold results is second */
|
|
if (! get_argument(0, AWK_STRING, & file_param)) {
|
|
warning(ext_id, _("stat: first argument is not a string"));
|
|
return make_number(-1, result);
|
|
}
|
|
|
|
if (! get_argument(1, AWK_ARRAY, & array_param)) {
|
|
warning(ext_id, _("stat: second argument is not an array"));
|
|
return make_number(-1, result);
|
|
}
|
|
|
|
if (nargs == 3) {
|
|
statfunc = stat;
|
|
}
|
|
|
|
name = file_param.str_value.str;
|
|
array = array_param.array_cookie;
|
|
|
|
/* always empty out the array */
|
|
clear_array(array);
|
|
|
|
/* stat the file; if error, set ERRNO and return */
|
|
ret = statfunc(name, & sbuf);
|
|
if (ret < 0) {
|
|
update_ERRNO_int(errno);
|
|
return make_number(ret, result);
|
|
}
|
|
|
|
ret = fill_stat_array(name, array, & sbuf);
|
|
|
|
return make_number(ret, result);
|
|
}
|
|
|
|
#if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
|
|
|
|
/* do_statvfs --- provide a statvfs() function for gawk */
|
|
|
|
static awk_value_t *
|
|
do_statvfs(int nargs, awk_value_t *result, struct awk_ext_func *unused)
|
|
{
|
|
awk_value_t file_param, array_param;
|
|
char *name;
|
|
awk_array_t array;
|
|
int ret;
|
|
struct statvfs vfsbuf;
|
|
|
|
assert(result != NULL);
|
|
|
|
/* file is first arg, array to hold results is second */
|
|
if ( ! get_argument(0, AWK_STRING, & file_param)
|
|
|| ! get_argument(1, AWK_ARRAY, & array_param)) {
|
|
warning(ext_id, _("stat: bad parameters"));
|
|
return make_number(-1, result);
|
|
}
|
|
|
|
name = file_param.str_value.str;
|
|
array = array_param.array_cookie;
|
|
|
|
/* always empty out the array */
|
|
clear_array(array);
|
|
|
|
/* statvfs the filesystem; if error, set ERRNO and return */
|
|
ret = statvfs(name, & vfsbuf);
|
|
if (ret < 0) {
|
|
update_ERRNO_int(errno);
|
|
return make_number(ret, result);
|
|
}
|
|
|
|
array_set_numeric(array, "bsize", vfsbuf.f_bsize); /* filesystem block size */
|
|
array_set_numeric(array, "frsize", vfsbuf.f_frsize); /* fragment size */
|
|
array_set_numeric(array, "blocks", vfsbuf.f_blocks); /* size of fs in f_frsize units */
|
|
array_set_numeric(array, "bfree", vfsbuf.f_bfree); /* # free blocks */
|
|
array_set_numeric(array, "bavail", vfsbuf.f_bavail); /* # free blocks for unprivileged users */
|
|
array_set_numeric(array, "files", vfsbuf.f_files); /* # inodes */
|
|
array_set_numeric(array, "ffree", vfsbuf.f_ffree); /* # free inodes */
|
|
array_set_numeric(array, "favail", vfsbuf.f_favail); /* # free inodes for unprivileged users */
|
|
#ifndef _AIX
|
|
array_set_numeric(array, "fsid", vfsbuf.f_fsid); /* filesystem ID */
|
|
#endif
|
|
array_set_numeric(array, "flag", vfsbuf.f_flag); /* mount flags */
|
|
array_set_numeric(array, "namemax", vfsbuf.f_namemax); /* maximum filename length */
|
|
|
|
|
|
return make_number(ret, result);
|
|
}
|
|
#endif
|
|
|
|
/* init_filefuncs --- initialization routine */
|
|
|
|
static awk_bool_t
|
|
init_filefuncs(void)
|
|
{
|
|
int errors = 0;
|
|
int i;
|
|
awk_value_t value;
|
|
|
|
#ifndef __MINGW32__
|
|
/* at least right now, only FTS needs initializing */
|
|
#define FTS_NON_RECURSIVE FTS_STOP /* Don't step into directories. */
|
|
static struct flagtab {
|
|
const char *name;
|
|
int value;
|
|
} opentab[] = {
|
|
#define ENTRY(x) { #x, x }
|
|
ENTRY(FTS_COMFOLLOW),
|
|
ENTRY(FTS_LOGICAL),
|
|
ENTRY(FTS_NOCHDIR),
|
|
ENTRY(FTS_PHYSICAL),
|
|
ENTRY(FTS_SEEDOT),
|
|
ENTRY(FTS_XDEV),
|
|
{"FTS_SKIP", FTS_NON_RECURSIVE},
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
for (i = 0; opentab[i].name != NULL; i++) {
|
|
(void) make_number(opentab[i].value, & value);
|
|
if (! sym_update(opentab[i].name, & value)) {
|
|
warning(ext_id, _("fts init: could not create variable %s"),
|
|
opentab[i].name);
|
|
errors++;
|
|
}
|
|
}
|
|
#endif
|
|
return errors == 0;
|
|
}
|
|
|
|
#ifdef __MINGW32__
|
|
/* do_fts --- walk a hierarchy and fill in an array */
|
|
|
|
/*
|
|
* Usage from awk:
|
|
* flags = or(FTS_PHYSICAL, ...)
|
|
* result = fts(pathlist, flags, filedata)
|
|
*/
|
|
|
|
static awk_value_t *
|
|
do_fts(int nargs, awk_value_t *result, struct awk_ext_func *unused)
|
|
{
|
|
fatal(ext_id, _("fts is not supported on this system"));
|
|
|
|
return NULL; /* for the compiler */
|
|
}
|
|
|
|
#else /* __MINGW32__ */
|
|
|
|
static int fts_errors = 0;
|
|
|
|
/* fill_stat_element --- fill in stat element of array */
|
|
|
|
static void
|
|
fill_stat_element(awk_array_t element_array, const char *name, struct stat *sbuf)
|
|
{
|
|
awk_value_t index, value;
|
|
awk_array_t stat_array;
|
|
|
|
stat_array = create_array();
|
|
if (stat_array == NULL) {
|
|
warning(ext_id, _("fill_stat_element: could not create array, out of memory"));
|
|
fts_errors++;
|
|
return;
|
|
}
|
|
fill_stat_array(name, stat_array, sbuf);
|
|
(void) make_const_string("stat", 4, & index);
|
|
value.val_type = AWK_ARRAY;
|
|
value.array_cookie = stat_array;
|
|
if (! set_array_element(element_array, & index, & value)) {
|
|
warning(ext_id, _("fill_stat_element: could not set element"));
|
|
fts_errors++;
|
|
}
|
|
}
|
|
|
|
/* fill_path_element --- fill in path element of array */
|
|
|
|
static void
|
|
fill_path_element(awk_array_t element_array, const char *path)
|
|
{
|
|
awk_value_t index, value;
|
|
|
|
(void) make_const_string("path", 4, & index);
|
|
(void) make_const_string(path, strlen(path), & value);
|
|
if (! set_array_element(element_array, & index, & value)) {
|
|
warning(ext_id, _("fill_path_element: could not set element"));
|
|
fts_errors++;
|
|
}
|
|
}
|
|
|
|
/* fill_error_element --- fill in error element of array */
|
|
|
|
static void
|
|
fill_error_element(awk_array_t element_array, const int errcode)
|
|
{
|
|
awk_value_t index, value;
|
|
const char *err = strerror(errcode);
|
|
|
|
(void) make_const_string("error", 5, & index);
|
|
(void) make_const_string(err, strlen(err), & value);
|
|
if (! set_array_element(element_array, & index, & value)) {
|
|
warning(ext_id, _("fill_error_element: could not set element"));
|
|
fts_errors++;
|
|
}
|
|
}
|
|
|
|
/* fill_default_elements --- fill in stat and path elements */
|
|
|
|
static void
|
|
fill_default_elements(awk_array_t element_array, const FTSENT *const fentry, awk_bool_t bad_ret)
|
|
{
|
|
/* full path */
|
|
fill_path_element(element_array, fentry->fts_path);
|
|
|
|
/* stat info */
|
|
if (! bad_ret) {
|
|
fill_stat_element(element_array,
|
|
fentry->fts_name,
|
|
fentry->fts_statp);
|
|
}
|
|
|
|
/* error info */
|
|
if (bad_ret || fentry->fts_errno != 0) {
|
|
fill_error_element(element_array, fentry->fts_errno);
|
|
}
|
|
}
|
|
|
|
/* process --- process the hierarchy */
|
|
|
|
static void
|
|
process(FTS *hierarchy, awk_array_t destarray, int seedot, int skipset)
|
|
{
|
|
FTSENT *fentry;
|
|
awk_value_t index, value;
|
|
awk_array_t element_array, newdir_array, dot_array;
|
|
awk_bool_t bad_ret = awk_false;
|
|
|
|
/* path is full path, pathlen is length thereof */
|
|
/* name is name in directory, namelen is length thereof */
|
|
while ((fentry = fts_read(hierarchy)) != NULL) {
|
|
bad_ret = awk_false;
|
|
|
|
switch (fentry->fts_info) {
|
|
case FTS_D:
|
|
/* directory */
|
|
|
|
if (skipset && fentry->fts_level == 0)
|
|
fts_set(hierarchy, fentry, FTS_SKIP);
|
|
|
|
/* create array to hold entries */
|
|
/* this will be empty if doing FTS_SKIP */
|
|
newdir_array = create_array();
|
|
if (newdir_array == NULL) {
|
|
warning(ext_id, _("fts-process: could not create array"));
|
|
fts_errors++;
|
|
break;
|
|
}
|
|
|
|
/* store new directory in its parent directory */
|
|
(void) make_const_string(fentry->fts_name, fentry->fts_namelen, & index);
|
|
value.val_type = AWK_ARRAY;
|
|
value.array_cookie = newdir_array;
|
|
if (! set_array_element(destarray, & index, & value)) {
|
|
warning(ext_id, _("fts-process: could not set element"));
|
|
fts_errors++;
|
|
break;
|
|
}
|
|
newdir_array = value.array_cookie;
|
|
|
|
/* push current directory */
|
|
stack_push(destarray);
|
|
|
|
/* new directory becomes current */
|
|
destarray = newdir_array;
|
|
break;
|
|
|
|
case FTS_DNR:
|
|
case FTS_DC:
|
|
case FTS_ERR:
|
|
case FTS_NS:
|
|
/* error */
|
|
bad_ret = awk_true;
|
|
/* fall through */
|
|
|
|
case FTS_NSOK:
|
|
case FTS_SL:
|
|
case FTS_SLNONE:
|
|
case FTS_F:
|
|
case FTS_DOT:
|
|
/* if see dot, skip "." */
|
|
if (seedot && strcmp(fentry->fts_name, ".") == 0)
|
|
break;
|
|
|
|
/*
|
|
* File case.
|
|
* destarray is the directory we're reading.
|
|
* step 1: create new empty array
|
|
*/
|
|
element_array = create_array();
|
|
if (element_array == NULL) {
|
|
warning(ext_id, _("fts-process: could not create array"));
|
|
fts_errors++;
|
|
break;
|
|
}
|
|
|
|
/* step 2: add element array to parent array */
|
|
(void) make_const_string(fentry->fts_name, fentry->fts_namelen, & index);
|
|
value.val_type = AWK_ARRAY;
|
|
value.array_cookie = element_array;
|
|
if (! set_array_element(destarray, & index, & value)) {
|
|
warning(ext_id, _("fts-process: could not set element"));
|
|
fts_errors++;
|
|
break;
|
|
}
|
|
|
|
/* step 3: fill in path, stat, error elements */
|
|
fill_default_elements(element_array, fentry, bad_ret);
|
|
break;
|
|
|
|
case FTS_DP:
|
|
/* create "." subarray */
|
|
dot_array = create_array();
|
|
|
|
/* add it to parent */
|
|
(void) make_const_string(".", 1, & index);
|
|
value.val_type = AWK_ARRAY;
|
|
value.array_cookie = dot_array;
|
|
if (! set_array_element(destarray, & index, & value)) {
|
|
warning(ext_id, _("fts-process: could not set element"));
|
|
fts_errors++;
|
|
break;
|
|
}
|
|
|
|
/* fill it in with path, stat, error elements */
|
|
fill_default_elements(dot_array, fentry, bad_ret);
|
|
|
|
/* now pop the parent directory off the stack */
|
|
if (! stack_empty()) {
|
|
/* pop stack */
|
|
destarray = stack_pop();
|
|
}
|
|
|
|
break;
|
|
|
|
case FTS_DEFAULT:
|
|
/* nothing to do */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* do_fts --- walk a hierarchy and fill in an array */
|
|
|
|
/*
|
|
* Usage from awk:
|
|
* flags = or(FTS_PHYSICAL, ...)
|
|
* result = fts(pathlist, flags, filedata)
|
|
*/
|
|
|
|
static awk_value_t *
|
|
do_fts(int nargs, awk_value_t *result, struct awk_ext_func *unused)
|
|
{
|
|
awk_value_t pathlist, flagval, dest;
|
|
awk_flat_array_t *path_array = NULL;
|
|
char **pathvector = NULL;
|
|
FTS *hierarchy;
|
|
int flags;
|
|
size_t i, count;
|
|
int ret = -1;
|
|
static const int mask = (
|
|
FTS_COMFOLLOW | FTS_LOGICAL | FTS_NOCHDIR | FTS_PHYSICAL
|
|
| FTS_SEEDOT | FTS_XDEV | FTS_NON_RECURSIVE);
|
|
|
|
assert(result != NULL);
|
|
fts_errors = 0; /* ensure a fresh start */
|
|
|
|
if (nargs > 3)
|
|
lintwarn(ext_id, _("fts: called with incorrect number of arguments, expecting 3"));
|
|
|
|
if (! get_argument(0, AWK_ARRAY, & pathlist)) {
|
|
warning(ext_id, _("fts: first argument is not an array"));
|
|
update_ERRNO_int(EINVAL);
|
|
goto out;
|
|
}
|
|
|
|
if (! get_argument(1, AWK_NUMBER, & flagval)) {
|
|
warning(ext_id, _("fts: second argument is not a number"));
|
|
update_ERRNO_int(EINVAL);
|
|
goto out;
|
|
}
|
|
|
|
if (! get_argument(2, AWK_ARRAY, & dest)) {
|
|
warning(ext_id, _("fts: third argument is not an array"));
|
|
update_ERRNO_int(EINVAL);
|
|
goto out;
|
|
}
|
|
|
|
/* flatten pathlist */
|
|
if (! flatten_array(pathlist.array_cookie, & path_array)) {
|
|
warning(ext_id, _("fts: could not flatten array\n"));
|
|
goto out;
|
|
}
|
|
|
|
/* check the flags first, before the array flattening */
|
|
|
|
/* get flags */
|
|
flags = flagval.num_value;
|
|
|
|
/* enforce physical or logical but not both, and not no_stat */
|
|
if ((flags & (FTS_PHYSICAL|FTS_LOGICAL)) == 0
|
|
|| (flags & (FTS_PHYSICAL|FTS_LOGICAL)) == (FTS_PHYSICAL|FTS_LOGICAL)) {
|
|
update_ERRNO_int(EINVAL);
|
|
goto out;
|
|
}
|
|
if ((flags & FTS_NOSTAT) != 0) {
|
|
flags &= ~FTS_NOSTAT;
|
|
if (do_lint)
|
|
lintwarn(ext_id, _("fts: ignoring sneaky FTS_NOSTAT flag. nyah, nyah, nyah."));
|
|
}
|
|
flags &= mask; /* turn off anything else */
|
|
|
|
if (flags & FTS_NON_RECURSIVE)
|
|
flags |= FTS_NOCHDIR;
|
|
|
|
/* make pathvector */
|
|
count = path_array->count + 1;
|
|
ezalloc(pathvector, char **, count * sizeof(char *), "do_fts");
|
|
|
|
/* fill it in */
|
|
count--; /* ignore final NULL at end of vector */
|
|
for (i = 0; i < count; i++)
|
|
pathvector[i] = path_array->elements[i].value.str_value.str;
|
|
|
|
|
|
/* clear dest array */
|
|
assert(clear_array(dest.array_cookie));
|
|
|
|
/* let's do it! */
|
|
hierarchy = fts_open(pathvector, flags & ~FTS_NON_RECURSIVE, NULL);
|
|
if (hierarchy != NULL) {
|
|
process(hierarchy, dest.array_cookie,
|
|
(flags & FTS_SEEDOT) != 0,
|
|
(flags & FTS_NON_RECURSIVE) != 0);
|
|
fts_close(hierarchy);
|
|
|
|
if (fts_errors == 0)
|
|
ret = 0;
|
|
} else
|
|
update_ERRNO_int(errno);
|
|
|
|
out:
|
|
if (pathvector != NULL)
|
|
gawk_free(pathvector);
|
|
if (path_array != NULL)
|
|
(void) release_flattened_array(pathlist.array_cookie, path_array);
|
|
|
|
return make_number(ret, result);
|
|
}
|
|
#endif /* ! __MINGW32__ */
|
|
|
|
static awk_ext_func_t func_table[] = {
|
|
{ "chdir", do_chdir, 1, 1, awk_false, NULL },
|
|
{ "stat", do_stat, 3, 2, awk_false, NULL },
|
|
#ifndef __MINGW32__
|
|
{ "fts", do_fts, 3, 3, awk_false, NULL },
|
|
#endif
|
|
#if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
|
|
{ "statvfs", do_statvfs, 2, 2, awk_false, NULL },
|
|
#endif
|
|
};
|
|
|
|
|
|
/* define the dl_load function using the boilerplate macro */
|
|
|
|
dl_load_func(func_table, filefuncs, "")
|