diff options
Diffstat (limited to 'extension/readdir.c')
-rw-r--r-- | extension/readdir.c | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/extension/readdir.c b/extension/readdir.c new file mode 100644 index 00000000..7d126d03 --- /dev/null +++ b/extension/readdir.c @@ -0,0 +1,343 @@ +/* + * readdir.c --- Provide an input parser to read directories + * + * Arnold Robbins + * arnold@skeeve.com + * Written 7/2012 + * + * Andrew Schorr and Arnold Robbins: further fixes 8/2012. + */ + +/* + * Copyright (C) 2012 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 +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_DIRENT_H +#include <dirent.h> +#else +#error Cannot compile the dirent extension on this system! +#endif + +#include "gawkapi.h" + +#include "gettext.h" +#define _(msgid) gettext(msgid) +#define N_(msgid) msgid + +static const gawk_api_t *api; /* for convenience macros to work */ +static awk_ext_id_t *ext_id; +static const char *ext_version = "readdir extension: version 1.0"; + +static awk_bool_t init_readdir(void); +static awk_bool_t (*init_func)(void) = init_readdir; + +int plugin_is_GPL_compatible; + +enum { + NEVER_DO_INFO, + USE_DIRENT_INFO, + USE_STAT_INFO +} do_ftype = +#ifdef DT_BLK + USE_DIRENT_INFO +#else + NEVER_DO_INFO +#endif + ; + +/* ftype --- return type of file as a single character string */ + +static const char * +ftype(struct dirent *entry) +{ + struct stat sbuf; + +#ifdef DT_BLK + switch (entry->d_type) { + case DT_BLK: return "b"; + case DT_CHR: return "c"; + case DT_DIR: return "d"; + case DT_FIFO: return "p"; + case DT_LNK: return "l"; + case DT_REG: return "f"; + case DT_SOCK: return "s"; + default: + case DT_UNKNOWN: + /* + * Could be that filesystem doesn't support d_type, + * even if the OS does. (E.g., XFS on GNU/Linux). + * So let lstat() do it. + */ + break; + } +#endif + + if (do_ftype < USE_STAT_INFO) + /* + * Avoid "/u" since user did not insist on file type info, + * and it does not seem to be supported by dirent on this + * filesystem. + */ + return NULL; + + /* Should we set ERRNO here? */ + if (lstat(entry->d_name, & sbuf) < 0) + return "u"; + + switch (sbuf.st_mode & S_IFMT) { + case S_IFREG: return "f"; + case S_IFBLK: return "b"; + case S_IFCHR: return "c"; + case S_IFDIR: return "d"; +#ifdef S_IFSOCK + case S_IFSOCK: return "s"; +#endif +#ifdef S_IFIFO + case S_IFIFO: return "p"; +#endif +#ifdef S_IFLNK + case S_IFLNK: return "l"; +#endif +#ifdef S_IFDOOR /* Solaris weirdness */ + case S_IFDOOR: return "D"; +#endif /* S_IFDOOR */ + } + return "u"; +} + +/* data type for the opaque pointer: */ + +typedef struct open_directory { + DIR *dp; + char *buf; +} open_directory_t; + +/* dir_get_record --- get one record at a time out of a directory */ + +static int +dir_get_record(char **out, awk_input_buf_t *iobuf, int *errcode, + char **rt_start, size_t *rt_len) +{ + DIR *dp; + struct dirent *dirent; + int len; + open_directory_t *the_dir; + + /* + * The caller sets *errcode to 0, so we should set it only if an + * error occurs. + */ + + if (out == NULL || iobuf == NULL || iobuf->opaque == NULL) + return EOF; + + the_dir = (open_directory_t *) iobuf->opaque; + dp = the_dir->dp; + + /* + * Initialize errno, since readdir does not set it to zero on EOF. + */ + errno = 0; + dirent = readdir(dp); + if (dirent == NULL) { + *errcode = errno; /* in case there was an error */ + return EOF; + } + +#ifdef ZOS_USS + len = sprintf(the_dir->buf, "%lu/%s", + (unsigned long) dirent->d_ino, + dirent->d_name); +#else + len = sprintf(the_dir->buf, "%llu/%s", + (unsigned long long) dirent->d_ino, + dirent->d_name); +#endif + + if (do_ftype != NEVER_DO_INFO) { + const char *ftstr = ftype(dirent); + if (ftstr) + len += sprintf(the_dir->buf + len, "/%s", ftstr); + } + + *out = the_dir->buf; + + *rt_len = 0; /* set RT to "" */ + return len; +} + +/* dir_close --- close up when done */ + +static void +dir_close(awk_input_buf_t *iobuf) +{ + open_directory_t *the_dir; + + if (iobuf == NULL || iobuf->opaque == NULL) + return; + + the_dir = (open_directory_t *) iobuf->opaque; + + closedir(the_dir->dp); + free(the_dir->buf); + free(the_dir); + + iobuf->fd = -1; +} + +/* dir_can_take_file --- return true if we want the file */ + +static awk_bool_t +dir_can_take_file(const awk_input_buf_t *iobuf) +{ + if (iobuf == NULL) + return 0; + + return (iobuf->fd != INVALID_HANDLE && S_ISDIR(iobuf->sbuf.st_mode)); +} + +/* + * dir_take_control_of --- set up input parser. + * We can assume that dir_can_take_file just returned true, + * and no state has changed since then. + */ + +static awk_bool_t +dir_take_control_of(awk_input_buf_t *iobuf) +{ + DIR *dp; + open_directory_t *the_dir; + size_t size; + + errno = 0; +#ifdef HAVE_FDOPENDIR + dp = fdopendir(iobuf->fd); +#else + dp = opendir(iobuf->name); + if (dp != NULL) + iobuf->fd = dirfd(dp); +#endif + if (dp == NULL) { + warning(ext_id, _("dir_take_control_of: opendir/fdopendir failed: %s"), + strerror(errno)); + update_ERRNO_int(errno); + return 0; + } + + emalloc(the_dir, open_directory_t *, sizeof(open_directory_t), "dir_take_control_of"); + the_dir->dp = dp; + size = sizeof(struct dirent) + 21 /* max digits in inode */ + 2 /* slashes */; + emalloc(the_dir->buf, char *, size, "dir_take_control_of"); + + iobuf->opaque = the_dir; + iobuf->get_record = dir_get_record; + iobuf->close_func = dir_close; + + return 1; +} + +static awk_input_parser_t readdir_parser = { + "readdir", + dir_can_take_file, + dir_take_control_of, + NULL +}; + +#ifdef TEST_DUPLICATE +static awk_input_parser_t readdir_parser2 = { + "readdir2", + dir_can_take_file, + dir_take_control_of, + NULL +}; +#endif + +/* init_readdir --- set things ups */ + +static awk_bool_t +init_readdir() +{ + register_input_parser(& readdir_parser); +#ifdef TEST_DUPLICATE + register_input_parser(& readdir_parser2); +#endif + + return 1; +} + +/* do_readdir_do_ftype --- enable / disable ftype where need to do stat */ + +static awk_value_t * +do_readdir_do_ftype(int nargs, awk_value_t *result) +{ + awk_value_t flag; + + make_number(1.0, result); + if (nargs < 1) { + warning(ext_id, _("readdir_do_ftype: called with no arguments")); + update_ERRNO_int(EINVAL); + make_number(0.0, result); + goto out; + } else if (do_lint && nargs > 3) + lintwarn(ext_id, _("readdir_do_ftype: called with more than one argument")); + + if (! get_argument(0, AWK_STRING, & flag)) { + warning(ext_id, _("readdir_do_ftype: could not get argument")); + update_ERRNO_int(EINVAL); + make_number(0.0, result); + goto out; + } + + if (strcmp(flag.str_value.str, "never") == 0) + do_ftype = NEVER_DO_INFO; + else if (strcmp(flag.str_value.str, "dirent") == 0) + do_ftype = USE_DIRENT_INFO; + else if (strcmp(flag.str_value.str, "stat") == 0) + do_ftype = USE_STAT_INFO; + else { + update_ERRNO_int(EINVAL); + make_number(0.0, result); + } + +out: + return result; +} + +static awk_ext_func_t func_table[] = { + { "readdir_do_ftype", do_readdir_do_ftype, 1 }, +}; + +/* define the dl_load function using the boilerplate macro */ + +dl_load_func(func_table, readdir, "") |