From 389378ae4f62653a77d6b1679989734715a0fc22 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Sun, 21 Mar 2010 22:16:58 -0400 Subject: [PATCH] Move IO API to src/io.[ch] --- Makefile | 7 +- io.c | 441 +++++++++++++++++++++++++++++++++++++++++++++++++++++ io.h | 76 ++++++++++ tig.c | 450 +------------------------------------------------------ 4 files changed, 522 insertions(+), 452 deletions(-) create mode 100644 io.c create mode 100644 io.h diff --git a/Makefile b/Makefile index e9670fb..78131f5 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ LDLIBS ?= -lcurses CFLAGS ?= -Wall -O2 DFLAGS = -g -DDEBUG -Werror -O0 PROGS = tig -SOURCE = tig.c tig.h +SOURCE = tig.c tig.h io.c io.h TXTDOC = tig.1.txt tigrc.5.txt manual.txt NEWS README INSTALL BUGS TODO MANDOC = tig.1 tigrc.5 tigmanual.7 HTMLDOC = tig.1.html tigrc.5.html manual.html README.html NEWS.html @@ -149,8 +149,9 @@ configure: configure.ac acinclude.m4 .PHONY: all all-debug doc doc-man doc-html install install-doc \ install-doc-man install-doc-html clean spell-check dist rpm -tig.o: tig.c tig.h -tig: tig.o +io.o: io.c io.h tig.h +tig.o: tig.c tig.h io.h +tig: tig.o io.o tig.spec: contrib/tig.spec.in sed -e 's/@@VERSION@@/$(RPM_VERSION)/g' \ diff --git a/io.c b/io.c new file mode 100644 index 0000000..685ffb6 --- /dev/null +++ b/io.c @@ -0,0 +1,441 @@ +/* Copyright (c) 2006-2010 Jonas Fonseca + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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. + */ + +#include "tig.h" +#include "io.h" + +bool +argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd) +{ + int valuelen; + + while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) { + bool advance = cmd[valuelen] != 0; + + cmd[valuelen] = 0; + argv[(*argc)++] = chomp_string(cmd); + cmd = chomp_string(cmd + valuelen + advance); + } + + if (*argc < SIZEOF_ARG) + argv[*argc] = NULL; + return *argc < SIZEOF_ARG; +} + +bool +argv_from_env(const char **argv, const char *name) +{ + char *env = argv ? getenv(name) : NULL; + int argc = 0; + + if (env && *env) + env = strdup(env); + return !env || argv_from_string(argv, &argc, env); +} + +void +argv_free(const char *argv[]) +{ + int argc; + + if (!argv) + return; + for (argc = 0; argv[argc]; argc++) + free((void *) argv[argc]); + argv[0] = NULL; +} + +size_t +argv_size(const char **argv) +{ + int argc = 0; + + while (argv && argv[argc]) + argc++; + + return argc; +} + +DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG) + +bool +argv_append(const char ***argv, const char *arg) +{ + size_t argc = argv_size(*argv); + + if (!argv_realloc(argv, argc, 2)) + return FALSE; + + (*argv)[argc++] = strdup(arg); + (*argv)[argc] = NULL; + return TRUE; +} + +bool +argv_append_array(const char ***dst_argv, const char *src_argv[]) +{ + int i; + + for (i = 0; src_argv && src_argv[i]; i++) + if (!argv_append(dst_argv, src_argv[i])) + return FALSE; + return TRUE; +} + +bool +argv_copy(const char ***dst, const char *src[]) +{ + int argc; + + for (argc = 0; src[argc]; argc++) + if (!argv_append(dst, src[argc])) + return FALSE; + return TRUE; +} + + +/* + * Executing external commands. + */ + +static void +io_init(struct io *io) +{ + memset(io, 0, sizeof(*io)); + io->pipe = -1; +} + +bool +io_open(struct io *io, const char *fmt, ...) +{ + char name[SIZEOF_STR] = ""; + bool fits; + va_list args; + + io_init(io); + + va_start(args, fmt); + fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name); + va_end(args); + + if (!fits) { + io->error = ENAMETOOLONG; + return FALSE; + } + io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO; + if (io->pipe == -1) + io->error = errno; + return io->pipe != -1; +} + +bool +io_kill(struct io *io) +{ + return io->pid == 0 || kill(io->pid, SIGKILL) != -1; +} + +bool +io_done(struct io *io) +{ + pid_t pid = io->pid; + + if (io->pipe != -1) + close(io->pipe); + free(io->buf); + io_init(io); + + while (pid > 0) { + int status; + pid_t waiting = waitpid(pid, &status, 0); + + if (waiting < 0) { + if (errno == EINTR) + continue; + io->error = errno; + return FALSE; + } + + return waiting == pid && + !WIFSIGNALED(status) && + WIFEXITED(status) && + !WEXITSTATUS(status); + } + + return TRUE; +} + +bool +io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...) +{ + int pipefds[2] = { -1, -1 }; + va_list args; + + io_init(io); + + if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) { + io->error = errno; + return FALSE; + } else if (type == IO_AP) { + va_start(args, argv); + pipefds[1] = va_arg(args, int); + va_end(args); + } + + if ((io->pid = fork())) { + if (io->pid == -1) + io->error = errno; + if (pipefds[!(type == IO_WR)] != -1) + close(pipefds[!(type == IO_WR)]); + if (io->pid != -1) { + io->pipe = pipefds[!!(type == IO_WR)]; + return TRUE; + } + + } else { + if (type != IO_FG) { + int devnull = open("/dev/null", O_RDWR); + int readfd = type == IO_WR ? pipefds[0] : devnull; + int writefd = (type == IO_RD || type == IO_AP) + ? pipefds[1] : devnull; + + dup2(readfd, STDIN_FILENO); + dup2(writefd, STDOUT_FILENO); + dup2(devnull, STDERR_FILENO); + + close(devnull); + if (pipefds[0] != -1) + close(pipefds[0]); + if (pipefds[1] != -1) + close(pipefds[1]); + } + + if (dir && *dir && chdir(dir) == -1) + exit(errno); + + execvp(argv[0], (char *const*) argv); + exit(errno); + } + + if (pipefds[!!(type == IO_WR)] != -1) + close(pipefds[!!(type == IO_WR)]); + return FALSE; +} + +bool +io_complete(enum io_type type, const char **argv, const char *dir, int fd) +{ + struct io io; + + return io_run(&io, type, dir, argv, fd) && io_done(&io); +} + +bool +io_run_bg(const char **argv) +{ + return io_complete(IO_BG, argv, NULL, -1); +} + +bool +io_run_fg(const char **argv, const char *dir) +{ + return io_complete(IO_FG, argv, dir, -1); +} + +bool +io_run_append(const char **argv, int fd) +{ + return io_complete(IO_AP, argv, NULL, fd); +} + +bool +io_eof(struct io *io) +{ + return io->eof; +} + +int +io_error(struct io *io) +{ + return io->error; +} + +char * +io_strerror(struct io *io) +{ + return strerror(io->error); +} + +bool +io_can_read(struct io *io) +{ + struct timeval tv = { 0, 500 }; + fd_set fds; + + FD_ZERO(&fds); + FD_SET(io->pipe, &fds); + + return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0; +} + +ssize_t +io_read(struct io *io, void *buf, size_t bufsize) +{ + do { + ssize_t readsize = read(io->pipe, buf, bufsize); + + if (readsize < 0 && (errno == EAGAIN || errno == EINTR)) + continue; + else if (readsize == -1) + io->error = errno; + else if (readsize == 0) + io->eof = 1; + return readsize; + } while (1); +} + +DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ) + +char * +io_get(struct io *io, int c, bool can_read) +{ + char *eol; + ssize_t readsize; + + while (TRUE) { + if (io->bufsize > 0) { + eol = memchr(io->bufpos, c, io->bufsize); + if (eol) { + char *line = io->bufpos; + + *eol = 0; + io->bufpos = eol + 1; + io->bufsize -= io->bufpos - line; + return line; + } + } + + if (io_eof(io)) { + if (io->bufsize) { + io->bufpos[io->bufsize] = 0; + io->bufsize = 0; + return io->bufpos; + } + return NULL; + } + + if (!can_read) + return NULL; + + if (io->bufsize > 0 && io->bufpos > io->buf) + memmove(io->buf, io->bufpos, io->bufsize); + + if (io->bufalloc == io->bufsize) { + if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ)) + return NULL; + io->bufalloc += BUFSIZ; + } + + io->bufpos = io->buf; + readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize); + if (io_error(io)) + return NULL; + io->bufsize += readsize; + } +} + +bool +io_write(struct io *io, const void *buf, size_t bufsize) +{ + size_t written = 0; + + while (!io_error(io) && written < bufsize) { + ssize_t size; + + size = write(io->pipe, buf + written, bufsize - written); + if (size < 0 && (errno == EAGAIN || errno == EINTR)) + continue; + else if (size == -1) + io->error = errno; + else + written += size; + } + + return written == bufsize; +} + +bool +io_read_buf(struct io *io, char buf[], size_t bufsize) +{ + char *result = io_get(io, '\n', TRUE); + + if (result) { + result = chomp_string(result); + string_ncopy_do(buf, bufsize, result, strlen(result)); + } + + return io_done(io) && result; +} + +bool +io_run_buf(const char **argv, char buf[], size_t bufsize) +{ + struct io io; + + return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize); +} + +int +io_load(struct io *io, const char *separators, + io_read_fn read_property, void *data) +{ + char *name; + int state = OK; + + while (state == OK && (name = io_get(io, '\n', TRUE))) { + char *value; + size_t namelen; + size_t valuelen; + + name = chomp_string(name); + namelen = strcspn(name, separators); + + if (name[namelen]) { + name[namelen] = 0; + value = chomp_string(name + namelen + 1); + valuelen = strlen(value); + + } else { + value = ""; + valuelen = 0; + } + + state = read_property(name, namelen, value, valuelen, data); + } + + if (state != ERR && io_error(io)) + state = ERR; + io_done(io); + + return state; +} + +int +io_run_load(const char **argv, const char *separators, + io_read_fn read_property, void *data) +{ + struct io io; + + if (!io_run(&io, IO_RD, NULL, argv)) + return ERR; + return io_load(&io, separators, read_property, data); +} diff --git a/io.h b/io.h new file mode 100644 index 0000000..9a68a5e --- /dev/null +++ b/io.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2006-2010 Jonas Fonseca + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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. + */ + +#ifndef TIG_IO_H +#define TIG_IO_H + +/* + * Argument array helpers. + */ + +bool argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd); +bool argv_from_env(const char **argv, const char *name); +void argv_free(const char *argv[]); +size_t argv_size(const char **argv); +bool argv_append(const char ***argv, const char *arg); +bool argv_append_array(const char ***dst_argv, const char *src_argv[]); +bool argv_copy(const char ***dst, const char *src[]); + +/* + * Executing external commands. + */ + +enum io_type { + IO_FD, /* File descriptor based IO. */ + IO_BG, /* Execute command in the background. */ + IO_FG, /* Execute command with same std{in,out,err}. */ + IO_RD, /* Read only fork+exec IO. */ + IO_WR, /* Write only fork+exec IO. */ + IO_AP, /* Append fork+exec output to file. */ +}; + +struct io { + int pipe; /* Pipe end for reading or writing. */ + pid_t pid; /* PID of spawned process. */ + int error; /* Error status. */ + char *buf; /* Read buffer. */ + size_t bufalloc; /* Allocated buffer size. */ + size_t bufsize; /* Buffer content size. */ + char *bufpos; /* Current buffer position. */ + unsigned int eof:1; /* Has end of file been reached. */ +}; + +typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data); + +bool io_open(struct io *io, const char *fmt, ...); +bool io_kill(struct io *io); +bool io_done(struct io *io); +bool io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...); +bool io_run_bg(const char **argv); +bool io_run_fg(const char **argv, const char *dir); +bool io_run_append(const char **argv, int fd); +bool io_eof(struct io *io); +int io_error(struct io *io); +char * io_strerror(struct io *io); +bool io_can_read(struct io *io); +ssize_t io_read(struct io *io, void *buf, size_t bufsize); +char * io_get(struct io *io, int c, bool can_read); +bool io_write(struct io *io, const void *buf, size_t bufsize); +bool io_read_buf(struct io *io, char buf[], size_t bufsize); +bool io_run_buf(const char **argv, char buf[], size_t bufsize); +int io_load(struct io *io, const char *separators, + io_read_fn read_property, void *data); +int io_run_load(const char **argv, const char *separators, + io_read_fn read_property, void *data); + +#endif diff --git a/tig.c b/tig.c index 718e6c2..6ad9c57 100644 --- a/tig.c +++ b/tig.c @@ -12,6 +12,7 @@ */ #include "tig.h" +#include "io.h" static void __NORETURN die(const char *err, ...); static void warn(const char *msg, ...); @@ -192,455 +193,6 @@ get_author_initials(const char *author) } -static bool -argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd) -{ - int valuelen; - - while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) { - bool advance = cmd[valuelen] != 0; - - cmd[valuelen] = 0; - argv[(*argc)++] = chomp_string(cmd); - cmd = chomp_string(cmd + valuelen + advance); - } - - if (*argc < SIZEOF_ARG) - argv[*argc] = NULL; - return *argc < SIZEOF_ARG; -} - -static bool -argv_from_env(const char **argv, const char *name) -{ - char *env = argv ? getenv(name) : NULL; - int argc = 0; - - if (env && *env) - env = strdup(env); - return !env || argv_from_string(argv, &argc, env); -} - -static void -argv_free(const char *argv[]) -{ - int argc; - - if (!argv) - return; - for (argc = 0; argv[argc]; argc++) - free((void *) argv[argc]); - argv[0] = NULL; -} - -static size_t -argv_size(const char **argv) -{ - int argc = 0; - - while (argv && argv[argc]) - argc++; - - return argc; -} - -DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG) - -static bool -argv_append(const char ***argv, const char *arg) -{ - size_t argc = argv_size(*argv); - - if (!argv_realloc(argv, argc, 2)) - return FALSE; - - (*argv)[argc++] = strdup(arg); - (*argv)[argc] = NULL; - return TRUE; -} - -static bool -argv_append_array(const char ***dst_argv, const char *src_argv[]) -{ - int i; - - for (i = 0; src_argv && src_argv[i]; i++) - if (!argv_append(dst_argv, src_argv[i])) - return FALSE; - return TRUE; -} - -static bool -argv_copy(const char ***dst, const char *src[]) -{ - int argc; - - for (argc = 0; src[argc]; argc++) - if (!argv_append(dst, src[argc])) - return FALSE; - return TRUE; -} - - -/* - * Executing external commands. - */ - -enum io_type { - IO_FD, /* File descriptor based IO. */ - IO_BG, /* Execute command in the background. */ - IO_FG, /* Execute command with same std{in,out,err}. */ - IO_RD, /* Read only fork+exec IO. */ - IO_WR, /* Write only fork+exec IO. */ - IO_AP, /* Append fork+exec output to file. */ -}; - -struct io { - int pipe; /* Pipe end for reading or writing. */ - pid_t pid; /* PID of spawned process. */ - int error; /* Error status. */ - char *buf; /* Read buffer. */ - size_t bufalloc; /* Allocated buffer size. */ - size_t bufsize; /* Buffer content size. */ - char *bufpos; /* Current buffer position. */ - unsigned int eof:1; /* Has end of file been reached. */ -}; - -static void -io_init(struct io *io) -{ - memset(io, 0, sizeof(*io)); - io->pipe = -1; -} - -static bool -io_open(struct io *io, const char *fmt, ...) -{ - char name[SIZEOF_STR] = ""; - bool fits; - va_list args; - - io_init(io); - - va_start(args, fmt); - fits = vsnprintf(name, sizeof(name), fmt, args) < sizeof(name); - va_end(args); - - if (!fits) { - io->error = ENAMETOOLONG; - return FALSE; - } - io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO; - if (io->pipe == -1) - io->error = errno; - return io->pipe != -1; -} - -static bool -io_kill(struct io *io) -{ - return io->pid == 0 || kill(io->pid, SIGKILL) != -1; -} - -static bool -io_done(struct io *io) -{ - pid_t pid = io->pid; - - if (io->pipe != -1) - close(io->pipe); - free(io->buf); - io_init(io); - - while (pid > 0) { - int status; - pid_t waiting = waitpid(pid, &status, 0); - - if (waiting < 0) { - if (errno == EINTR) - continue; - io->error = errno; - return FALSE; - } - - return waiting == pid && - !WIFSIGNALED(status) && - WIFEXITED(status) && - !WEXITSTATUS(status); - } - - return TRUE; -} - -static bool -io_run(struct io *io, enum io_type type, const char *dir, const char *argv[], ...) -{ - int pipefds[2] = { -1, -1 }; - va_list args; - - io_init(io); - - if ((type == IO_RD || type == IO_WR) && pipe(pipefds) < 0) { - io->error = errno; - return FALSE; - } else if (type == IO_AP) { - va_start(args, argv); - pipefds[1] = va_arg(args, int); - va_end(args); - } - - if ((io->pid = fork())) { - if (io->pid == -1) - io->error = errno; - if (pipefds[!(type == IO_WR)] != -1) - close(pipefds[!(type == IO_WR)]); - if (io->pid != -1) { - io->pipe = pipefds[!!(type == IO_WR)]; - return TRUE; - } - - } else { - if (type != IO_FG) { - int devnull = open("/dev/null", O_RDWR); - int readfd = type == IO_WR ? pipefds[0] : devnull; - int writefd = (type == IO_RD || type == IO_AP) - ? pipefds[1] : devnull; - - dup2(readfd, STDIN_FILENO); - dup2(writefd, STDOUT_FILENO); - dup2(devnull, STDERR_FILENO); - - close(devnull); - if (pipefds[0] != -1) - close(pipefds[0]); - if (pipefds[1] != -1) - close(pipefds[1]); - } - - if (dir && *dir && chdir(dir) == -1) - exit(errno); - - execvp(argv[0], (char *const*) argv); - exit(errno); - } - - if (pipefds[!!(type == IO_WR)] != -1) - close(pipefds[!!(type == IO_WR)]); - return FALSE; -} - -static bool -io_complete(enum io_type type, const char **argv, const char *dir, int fd) -{ - struct io io; - - return io_run(&io, type, dir, argv, fd) && io_done(&io); -} - -static bool -io_run_bg(const char **argv) -{ - return io_complete(IO_BG, argv, NULL, -1); -} - -static bool -io_run_fg(const char **argv, const char *dir) -{ - return io_complete(IO_FG, argv, dir, -1); -} - -static bool -io_run_append(const char **argv, int fd) -{ - return io_complete(IO_AP, argv, NULL, fd); -} - -static bool -io_eof(struct io *io) -{ - return io->eof; -} - -static int -io_error(struct io *io) -{ - return io->error; -} - -static char * -io_strerror(struct io *io) -{ - return strerror(io->error); -} - -static bool -io_can_read(struct io *io) -{ - struct timeval tv = { 0, 500 }; - fd_set fds; - - FD_ZERO(&fds); - FD_SET(io->pipe, &fds); - - return select(io->pipe + 1, &fds, NULL, NULL, &tv) > 0; -} - -static ssize_t -io_read(struct io *io, void *buf, size_t bufsize) -{ - do { - ssize_t readsize = read(io->pipe, buf, bufsize); - - if (readsize < 0 && (errno == EAGAIN || errno == EINTR)) - continue; - else if (readsize == -1) - io->error = errno; - else if (readsize == 0) - io->eof = 1; - return readsize; - } while (1); -} - -DEFINE_ALLOCATOR(io_realloc_buf, char, BUFSIZ) - -static char * -io_get(struct io *io, int c, bool can_read) -{ - char *eol; - ssize_t readsize; - - while (TRUE) { - if (io->bufsize > 0) { - eol = memchr(io->bufpos, c, io->bufsize); - if (eol) { - char *line = io->bufpos; - - *eol = 0; - io->bufpos = eol + 1; - io->bufsize -= io->bufpos - line; - return line; - } - } - - if (io_eof(io)) { - if (io->bufsize) { - io->bufpos[io->bufsize] = 0; - io->bufsize = 0; - return io->bufpos; - } - return NULL; - } - - if (!can_read) - return NULL; - - if (io->bufsize > 0 && io->bufpos > io->buf) - memmove(io->buf, io->bufpos, io->bufsize); - - if (io->bufalloc == io->bufsize) { - if (!io_realloc_buf(&io->buf, io->bufalloc, BUFSIZ)) - return NULL; - io->bufalloc += BUFSIZ; - } - - io->bufpos = io->buf; - readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize); - if (io_error(io)) - return NULL; - io->bufsize += readsize; - } -} - -static bool -io_write(struct io *io, const void *buf, size_t bufsize) -{ - size_t written = 0; - - while (!io_error(io) && written < bufsize) { - ssize_t size; - - size = write(io->pipe, buf + written, bufsize - written); - if (size < 0 && (errno == EAGAIN || errno == EINTR)) - continue; - else if (size == -1) - io->error = errno; - else - written += size; - } - - return written == bufsize; -} - -static bool -io_read_buf(struct io *io, char buf[], size_t bufsize) -{ - char *result = io_get(io, '\n', TRUE); - - if (result) { - result = chomp_string(result); - string_ncopy_do(buf, bufsize, result, strlen(result)); - } - - return io_done(io) && result; -} - -static bool -io_run_buf(const char **argv, char buf[], size_t bufsize) -{ - struct io io; - - return io_run(&io, IO_RD, NULL, argv) && io_read_buf(&io, buf, bufsize); -} - -typedef int (*io_read_fn)(char *, size_t, char *, size_t, void *data); - -static int -io_load(struct io *io, const char *separators, - io_read_fn read_property, void *data) -{ - char *name; - int state = OK; - - while (state == OK && (name = io_get(io, '\n', TRUE))) { - char *value; - size_t namelen; - size_t valuelen; - - name = chomp_string(name); - namelen = strcspn(name, separators); - - if (name[namelen]) { - name[namelen] = 0; - value = chomp_string(name + namelen + 1); - valuelen = strlen(value); - - } else { - value = ""; - valuelen = 0; - } - - state = read_property(name, namelen, value, valuelen, data); - } - - if (state != ERR && io_error(io)) - state = ERR; - io_done(io); - - return state; -} - -static int -io_run_load(const char **argv, const char *separators, - io_read_fn read_property, void *data) -{ - struct io io; - - if (!io_run(&io, IO_RD, NULL, argv)) - return ERR; - return io_load(&io, separators, read_property, data); -} - - /* * User requests */ -- 2.30.2