binutils-gdb/sim/common/callback.c
Joel Brobecker 32d0add0a6 Update year range in copyright notice of all files owned by the GDB project.
gdb/ChangeLog:

        Update year range in copyright notice of all files.
2015-01-01 13:32:14 +04:00

1048 lines
22 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Remote target callback routines.
Copyright 1995-2015 Free Software Foundation, Inc.
Contributed by Cygnus Solutions.
This file is part of GDB.
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 3 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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* This file provides a standard way for targets to talk to the host OS
level. */
#ifdef HAVE_CONFIG_H
#include "cconfig.h"
#endif
#include "config.h"
#include "ansidecl.h"
#include <stdarg.h>
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#else
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#endif
#ifdef HAVE_LIMITS_H
/* For PIPE_BUF. */
#include <limits.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "gdb/callback.h"
#include "targ-vals.h"
/* For xmalloc. */
#include "libiberty.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifndef PIPE_BUF
#define PIPE_BUF 512
#endif
/* ??? sim_cb_printf should be cb_printf, but until the callback support is
broken out of the simulator directory, these are here to not require
sim-utils.h. */
void sim_cb_printf (host_callback *, const char *, ...);
void sim_cb_eprintf (host_callback *, const char *, ...);
extern CB_TARGET_DEFS_MAP cb_init_syscall_map[];
extern CB_TARGET_DEFS_MAP cb_init_errno_map[];
extern CB_TARGET_DEFS_MAP cb_init_open_map[];
/* Set the callback copy of errno from what we see now. */
static int
wrap (host_callback *p, int val)
{
p->last_errno = errno;
return val;
}
/* Make sure the FD provided is ok. If not, return non-zero
and set errno. */
static int
fdbad (host_callback *p, int fd)
{
if (fd < 0 || fd > MAX_CALLBACK_FDS || p->fd_buddy[fd] < 0)
{
p->last_errno = EBADF;
return -1;
}
return 0;
}
static int
fdmap (host_callback *p, int fd)
{
return p->fdmap[fd];
}
static int
os_close (host_callback *p, int fd)
{
int result;
int i, next;
result = fdbad (p, fd);
if (result)
return result;
/* If this file descripter has one or more buddies (originals /
duplicates from a dup), just remove it from the circular list. */
for (i = fd; (next = p->fd_buddy[i]) != fd; )
i = next;
if (fd != i)
p->fd_buddy[i] = p->fd_buddy[fd];
else
{
if (p->ispipe[fd])
{
int other = p->ispipe[fd];
int reader, writer;
if (other > 0)
{
/* Closing the read side. */
reader = fd;
writer = other;
}
else
{
/* Closing the write side. */
writer = fd;
reader = -other;
}
/* If there was data in the buffer, make a last "now empty"
call, then deallocate data. */
if (p->pipe_buffer[writer].buffer != NULL)
{
(*p->pipe_empty) (p, reader, writer);
free (p->pipe_buffer[writer].buffer);
p->pipe_buffer[writer].buffer = NULL;
}
/* Clear pipe data for this side. */
p->pipe_buffer[fd].size = 0;
p->ispipe[fd] = 0;
/* If this was the first close, mark the other side as the
only remaining side. */
if (fd != abs (other))
p->ispipe[abs (other)] = -other;
p->fd_buddy[fd] = -1;
return 0;
}
result = wrap (p, close (fdmap (p, fd)));
}
p->fd_buddy[fd] = -1;
return result;
}
/* taken from gdb/util.c:notice_quit() - should be in a library */
#if defined(__GO32__) || defined (_MSC_VER)
static int
os_poll_quit (host_callback *p)
{
#if defined(__GO32__)
int kbhit ();
int getkey ();
if (kbhit ())
{
int k = getkey ();
if (k == 1)
{
return 1;
}
else if (k == 2)
{
return 1;
}
else
{
sim_cb_eprintf (p, "CTRL-A to quit, CTRL-B to quit harder\n");
}
}
#endif
#if defined (_MSC_VER)
/* NB - this will not compile! */
int k = win32pollquit ();
if (k == 1)
return 1;
else if (k == 2)
return 1;
#endif
return 0;
}
#else
#define os_poll_quit 0
#endif /* defined(__GO32__) || defined(_MSC_VER) */
static int
os_get_errno (host_callback *p)
{
return cb_host_to_target_errno (p, p->last_errno);
}
static int
os_isatty (host_callback *p, int fd)
{
int result;
result = fdbad (p, fd);
if (result)
return result;
result = wrap (p, isatty (fdmap (p, fd)));
return result;
}
static int
os_lseek (host_callback *p, int fd, long off, int way)
{
int result;
result = fdbad (p, fd);
if (result)
return result;
result = wrap (p, lseek (fdmap (p, fd), off, way));
return result;
}
static int
os_open (host_callback *p, const char *name, int flags)
{
int i;
for (i = 0; i < MAX_CALLBACK_FDS; i++)
{
if (p->fd_buddy[i] < 0)
{
int f = open (name, cb_target_to_host_open (p, flags), 0644);
if (f < 0)
{
p->last_errno = errno;
return f;
}
p->fd_buddy[i] = i;
p->fdmap[i] = f;
return i;
}
}
p->last_errno = EMFILE;
return -1;
}
static int
os_read (host_callback *p, int fd, char *buf, int len)
{
int result;
result = fdbad (p, fd);
if (result)
return result;
if (p->ispipe[fd])
{
int writer = p->ispipe[fd];
/* Can't read from the write-end. */
if (writer < 0)
{
p->last_errno = EBADF;
return -1;
}
/* Nothing to read if nothing is written. */
if (p->pipe_buffer[writer].size == 0)
return 0;
/* Truncate read request size to buffer size minus what's already
read. */
if (len > p->pipe_buffer[writer].size - p->pipe_buffer[fd].size)
len = p->pipe_buffer[writer].size - p->pipe_buffer[fd].size;
memcpy (buf, p->pipe_buffer[writer].buffer + p->pipe_buffer[fd].size,
len);
/* Account for what we just read. */
p->pipe_buffer[fd].size += len;
/* If we've read everything, empty and deallocate the buffer and
signal buffer-empty to client. (This isn't expected to be a
hot path in the simulator, so we don't hold on to the buffer.) */
if (p->pipe_buffer[fd].size == p->pipe_buffer[writer].size)
{
free (p->pipe_buffer[writer].buffer);
p->pipe_buffer[writer].buffer = NULL;
p->pipe_buffer[fd].size = 0;
p->pipe_buffer[writer].size = 0;
(*p->pipe_empty) (p, fd, writer);
}
return len;
}
result = wrap (p, read (fdmap (p, fd), buf, len));
return result;
}
static int
os_read_stdin (host_callback *p, char *buf, int len)
{
return wrap (p, read (0, buf, len));
}
static int
os_write (host_callback *p, int fd, const char *buf, int len)
{
int result;
int real_fd;
result = fdbad (p, fd);
if (result)
return result;
if (p->ispipe[fd])
{
int reader = -p->ispipe[fd];
/* Can't write to the read-end. */
if (reader < 0)
{
p->last_errno = EBADF;
return -1;
}
/* Can't write to pipe with closed read end.
FIXME: We should send a SIGPIPE. */
if (reader == fd)
{
p->last_errno = EPIPE;
return -1;
}
/* As a sanity-check, we bail out it the buffered contents is much
larger than the size of the buffer on the host. We don't want
to run out of memory in the simulator due to a target program
bug if we can help it. Unfortunately, regarding the value that
reaches the simulated program, it's no use returning *less*
than the requested amount, because cb_syscall loops calling
this function until the whole amount is done. */
if (p->pipe_buffer[fd].size + len > 10 * PIPE_BUF)
{
p->last_errno = EFBIG;
return -1;
}
p->pipe_buffer[fd].buffer
= xrealloc (p->pipe_buffer[fd].buffer, p->pipe_buffer[fd].size + len);
memcpy (p->pipe_buffer[fd].buffer + p->pipe_buffer[fd].size,
buf, len);
p->pipe_buffer[fd].size += len;
(*p->pipe_nonempty) (p, reader, fd);
return len;
}
real_fd = fdmap (p, fd);
switch (real_fd)
{
default:
result = wrap (p, write (real_fd, buf, len));
break;
case 1:
result = p->write_stdout (p, buf, len);
break;
case 2:
result = p->write_stderr (p, buf, len);
break;
}
return result;
}
static int
os_write_stdout (host_callback *p ATTRIBUTE_UNUSED, const char *buf, int len)
{
return fwrite (buf, 1, len, stdout);
}
static void
os_flush_stdout (host_callback *p ATTRIBUTE_UNUSED)
{
fflush (stdout);
}
static int
os_write_stderr (host_callback *p ATTRIBUTE_UNUSED, const char *buf, int len)
{
return fwrite (buf, 1, len, stderr);
}
static void
os_flush_stderr (host_callback *p ATTRIBUTE_UNUSED)
{
fflush (stderr);
}
static int
os_rename (host_callback *p, const char *f1, const char *f2)
{
return wrap (p, rename (f1, f2));
}
static int
os_system (host_callback *p, const char *s)
{
return wrap (p, system (s));
}
static long
os_time (host_callback *p, long *t)
{
return wrap (p, time (t));
}
static int
os_unlink (host_callback *p, const char *f1)
{
return wrap (p, unlink (f1));
}
static int
os_stat (host_callback *p, const char *file, struct stat *buf)
{
/* ??? There is an issue of when to translate to the target layout.
One could do that inside this function, or one could have the
caller do it. It's more flexible to let the caller do it, though
I'm not sure the flexibility will ever be useful. */
return wrap (p, stat (file, buf));
}
static int
os_fstat (host_callback *p, int fd, struct stat *buf)
{
if (fdbad (p, fd))
return -1;
if (p->ispipe[fd])
{
#if defined (HAVE_STRUCT_STAT_ST_ATIME) || defined (HAVE_STRUCT_STAT_ST_CTIME) || defined (HAVE_STRUCT_STAT_ST_MTIME)
time_t t = (*p->time) (p, NULL);
#endif
/* We have to fake the struct stat contents, since the pipe is
made up in the simulator. */
memset (buf, 0, sizeof (*buf));
#ifdef HAVE_STRUCT_STAT_ST_MODE
buf->st_mode = S_IFIFO;
#endif
/* If more accurate tracking than current-time is needed (for
example, on GNU/Linux we get accurate numbers), the p->time
callback (which may be something other than os_time) should
happen for each read and write, and we'd need to keep track of
atime, ctime and mtime. */
#ifdef HAVE_STRUCT_STAT_ST_ATIME
buf->st_atime = t;
#endif
#ifdef HAVE_STRUCT_STAT_ST_CTIME
buf->st_ctime = t;
#endif
#ifdef HAVE_STRUCT_STAT_ST_MTIME
buf->st_mtime = t;
#endif
return 0;
}
/* ??? There is an issue of when to translate to the target layout.
One could do that inside this function, or one could have the
caller do it. It's more flexible to let the caller do it, though
I'm not sure the flexibility will ever be useful. */
return wrap (p, fstat (fdmap (p, fd), buf));
}
static int
os_lstat (host_callback *p, const char *file, struct stat *buf)
{
/* NOTE: hpn/2004-12-12: Same issue here as with os_fstat. */
#ifdef HAVE_LSTAT
return wrap (p, lstat (file, buf));
#else
return wrap (p, stat (file, buf));
#endif
}
static int
os_ftruncate (host_callback *p, int fd, long len)
{
int result;
result = fdbad (p, fd);
if (p->ispipe[fd])
{
p->last_errno = EINVAL;
return -1;
}
if (result)
return result;
#ifdef HAVE_FTRUNCATE
result = wrap (p, ftruncate (fdmap (p, fd), len));
#else
p->last_errno = EINVAL;
result = -1;
#endif
return result;
}
static int
os_truncate (host_callback *p, const char *file, long len)
{
#ifdef HAVE_TRUNCATE
return wrap (p, truncate (file, len));
#else
p->last_errno = EINVAL;
return -1;
#endif
}
static int
os_pipe (host_callback *p, int *filedes)
{
int i;
/* We deliberately don't use fd 0. It's probably stdin anyway. */
for (i = 1; i < MAX_CALLBACK_FDS; i++)
{
int j;
if (p->fd_buddy[i] < 0)
for (j = i + 1; j < MAX_CALLBACK_FDS; j++)
if (p->fd_buddy[j] < 0)
{
/* Found two free fd:s. Set stat to allocated and mark
pipeness. */
p->fd_buddy[i] = i;
p->fd_buddy[j] = j;
p->ispipe[i] = j;
p->ispipe[j] = -i;
filedes[0] = i;
filedes[1] = j;
/* Poison the FD map to make bugs apparent. */
p->fdmap[i] = -1;
p->fdmap[j] = -1;
return 0;
}
}
p->last_errno = EMFILE;
return -1;
}
/* Stub functions for pipe support. They should always be overridden in
targets using the pipe support, but that's up to the target. */
/* Called when the simulator says that the pipe at (reader, writer) is
now empty (so the writer should leave its waiting state). */
static void
os_pipe_empty (host_callback *p, int reader, int writer)
{
}
/* Called when the simulator says the pipe at (reader, writer) is now
non-empty (so the writer should wait). */
static void
os_pipe_nonempty (host_callback *p, int reader, int writer)
{
}
static int
os_shutdown (host_callback *p)
{
int i, next, j;
for (i = 0; i < MAX_CALLBACK_FDS; i++)
{
int do_close = 1;
/* Zero out all pipe state. Don't call callbacks for non-empty
pipes; the target program has likely terminated at this point
or we're called at initialization time. */
p->ispipe[i] = 0;
p->pipe_buffer[i].size = 0;
p->pipe_buffer[i].buffer = NULL;
next = p->fd_buddy[i];
if (next < 0)
continue;
do
{
j = next;
if (j == MAX_CALLBACK_FDS)
do_close = 0;
next = p->fd_buddy[j];
p->fd_buddy[j] = -1;
/* At the initial call of os_init, we got -1, 0, 0, 0, ... */
if (next < 0)
{
p->fd_buddy[i] = -1;
do_close = 0;
break;
}
}
while (j != i);
if (do_close)
close (p->fdmap[i]);
}
return 1;
}
static int
os_init (host_callback *p)
{
int i;
os_shutdown (p);
for (i = 0; i < 3; i++)
{
p->fdmap[i] = i;
p->fd_buddy[i] = i - 1;
}
p->fd_buddy[0] = MAX_CALLBACK_FDS;
p->fd_buddy[MAX_CALLBACK_FDS] = 2;
p->syscall_map = cb_init_syscall_map;
p->errno_map = cb_init_errno_map;
p->open_map = cb_init_open_map;
return 1;
}
/* DEPRECATED */
/* VARARGS */
static void
os_printf_filtered (host_callback *p ATTRIBUTE_UNUSED, const char *format, ...)
{
va_list args;
va_start (args, format);
vfprintf (stdout, format, args);
va_end (args);
}
/* VARARGS */
static void
os_vprintf_filtered (host_callback *p ATTRIBUTE_UNUSED, const char *format, va_list args)
{
vprintf (format, args);
}
/* VARARGS */
static void
os_evprintf_filtered (host_callback *p ATTRIBUTE_UNUSED, const char *format, va_list args)
{
vfprintf (stderr, format, args);
}
/* VARARGS */
#ifdef __GNUC__
__attribute__ ((__noreturn__))
#endif
static void
os_error (host_callback *p ATTRIBUTE_UNUSED, const char *format, ...)
{
va_list args;
va_start (args, format);
vfprintf (stderr, format, args);
fprintf (stderr, "\n");
va_end (args);
exit (1);
}
host_callback default_callback =
{
os_close,
os_get_errno,
os_isatty,
os_lseek,
os_open,
os_read,
os_read_stdin,
os_rename,
os_system,
os_time,
os_unlink,
os_write,
os_write_stdout,
os_flush_stdout,
os_write_stderr,
os_flush_stderr,
os_stat,
os_fstat,
os_lstat,
os_ftruncate,
os_truncate,
os_pipe,
os_pipe_empty,
os_pipe_nonempty,
os_poll_quit,
os_shutdown,
os_init,
os_printf_filtered, /* deprecated */
os_vprintf_filtered,
os_evprintf_filtered,
os_error,
0, /* last errno */
{ 0, }, /* fdmap */
{ -1, }, /* fd_buddy */
{ 0, }, /* ispipe */
{ { 0, 0 }, }, /* pipe_buffer */
0, /* syscall_map */
0, /* errno_map */
0, /* open_map */
0, /* signal_map */
0, /* stat_map */
/* Defaults expected to be overridden at initialization, where needed. */
BFD_ENDIAN_UNKNOWN, /* target_endian */
4, /* target_sizeof_int */
HOST_CALLBACK_MAGIC,
};
/* Read in a file describing the target's system call values.
E.g. maybe someone will want to use something other than newlib.
This assumes that the basic system call recognition and value passing/
returning is supported. So maybe some coding/recompilation will be
necessary, but not as much.
If an error occurs, the existing mapping is not changed. */
CB_RC
cb_read_target_syscall_maps (host_callback *cb, const char *file)
{
CB_TARGET_DEFS_MAP *syscall_map, *errno_map, *open_map, *signal_map;
const char *stat_map;
FILE *f;
if ((f = fopen (file, "r")) == NULL)
return CB_RC_ACCESS;
/* ... read in and parse file ... */
fclose (f);
return CB_RC_NO_MEM; /* FIXME:wip */
/* Free storage allocated for any existing maps. */
if (cb->syscall_map)
free (cb->syscall_map);
if (cb->errno_map)
free (cb->errno_map);
if (cb->open_map)
free (cb->open_map);
if (cb->signal_map)
free (cb->signal_map);
if (cb->stat_map)
free ((PTR) cb->stat_map);
cb->syscall_map = syscall_map;
cb->errno_map = errno_map;
cb->open_map = open_map;
cb->signal_map = signal_map;
cb->stat_map = stat_map;
return CB_RC_OK;
}
/* Translate the target's version of a syscall number to the host's.
This isn't actually the host's version, rather a canonical form.
??? Perhaps this should be renamed to ..._canon_syscall. */
int
cb_target_to_host_syscall (host_callback *cb, int target_val)
{
CB_TARGET_DEFS_MAP *m;
for (m = &cb->syscall_map[0]; m->target_val != -1; ++m)
if (m->target_val == target_val)
return m->host_val;
return -1;
}
/* FIXME: sort tables if large.
Alternatively, an obvious improvement for errno conversion is
to machine generate a function with a large switch(). */
/* Translate the host's version of errno to the target's. */
int
cb_host_to_target_errno (host_callback *cb, int host_val)
{
CB_TARGET_DEFS_MAP *m;
for (m = &cb->errno_map[0]; m->host_val; ++m)
if (m->host_val == host_val)
return m->target_val;
/* ??? Which error to return in this case is up for grabs.
Note that some missing values may have standard alternatives.
For now return 0 and require caller to deal with it. */
return 0;
}
/* Given a set of target bitmasks for the open system call,
return the host equivalent.
Mapping open flag values is best done by looping so there's no need
to machine generate this function. */
int
cb_target_to_host_open (host_callback *cb, int target_val)
{
int host_val = 0;
CB_TARGET_DEFS_MAP *m;
for (m = &cb->open_map[0]; m->host_val != -1; ++m)
{
switch (m->target_val)
{
/* O_RDONLY can be (and usually is) 0 which needs to be treated
specially. */
case TARGET_O_RDONLY :
case TARGET_O_WRONLY :
case TARGET_O_RDWR :
if ((target_val & (TARGET_O_RDONLY | TARGET_O_WRONLY | TARGET_O_RDWR))
== m->target_val)
host_val |= m->host_val;
/* Handle the host/target differentiating between binary and
text mode. Only one case is of importance */
#if ! defined (TARGET_O_BINARY) && defined (O_BINARY)
host_val |= O_BINARY;
#endif
break;
default :
if ((m->target_val & target_val) == m->target_val)
host_val |= m->host_val;
break;
}
}
return host_val;
}
/* Utility for e.g. cb_host_to_target_stat to store values in the target's
stat struct.
??? The "val" must be as big as target word size. */
void
cb_store_target_endian (host_callback *cb, char *p, int size, long val)
{
if (cb->target_endian == BFD_ENDIAN_BIG)
{
p += size;
while (size-- > 0)
{
*--p = val;
val >>= 8;
}
}
else
{
while (size-- > 0)
{
*p++ = val;
val >>= 8;
}
}
}
/* Translate a host's stat struct into a target's.
If HS is NULL, just compute the length of the buffer required,
TS is ignored.
The result is the size of the target's stat struct,
or zero if an error occurred during the translation. */
int
cb_host_to_target_stat (host_callback *cb, const struct stat *hs, PTR ts)
{
const char *m = cb->stat_map;
char *p;
if (hs == NULL)
ts = NULL;
p = ts;
while (m)
{
char *q = strchr (m, ',');
int size;
/* FIXME: Use sscanf? */
if (q == NULL)
{
/* FIXME: print error message */
return 0;
}
size = atoi (q + 1);
if (size == 0)
{
/* FIXME: print error message */
return 0;
}
if (hs != NULL)
{
if (0)
;
/* Defined here to avoid emacs indigestion on a lone "else". */
#undef ST_x
#define ST_x(FLD) \
else if (strncmp (m, #FLD, q - m) == 0) \
cb_store_target_endian (cb, p, size, hs->FLD)
#ifdef HAVE_STRUCT_STAT_ST_DEV
ST_x (st_dev);
#endif
#ifdef HAVE_STRUCT_STAT_ST_INO
ST_x (st_ino);
#endif
#ifdef HAVE_STRUCT_STAT_ST_MODE
ST_x (st_mode);
#endif
#ifdef HAVE_STRUCT_STAT_ST_NLINK
ST_x (st_nlink);
#endif
#ifdef HAVE_STRUCT_STAT_ST_UID
ST_x (st_uid);
#endif
#ifdef HAVE_STRUCT_STAT_ST_GID
ST_x (st_gid);
#endif
#ifdef HAVE_STRUCT_STAT_ST_RDEV
ST_x (st_rdev);
#endif
#ifdef HAVE_STRUCT_STAT_ST_SIZE
ST_x (st_size);
#endif
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
ST_x (st_blksize);
#endif
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
ST_x (st_blocks);
#endif
#ifdef HAVE_STRUCT_STAT_ST_ATIME
ST_x (st_atime);
#endif
#ifdef HAVE_STRUCT_STAT_ST_MTIME
ST_x (st_mtime);
#endif
#ifdef HAVE_STRUCT_STAT_ST_CTIME
ST_x (st_ctime);
#endif
#undef ST_x
/* FIXME:wip */
else
/* Unsupported field, store 0. */
cb_store_target_endian (cb, p, size, 0);
}
p += size;
m = strchr (q, ':');
if (m)
++m;
}
return p - (char *) ts;
}
/* Cover functions to the vfprintf callbacks.
??? If one thinks of the callbacks as a subsystem onto itself [or part of
a larger "remote target subsystem"] with a well defined interface, then
one would think that the subsystem would provide these. However, until
one is allowed to create such a subsystem (with its own source tree
independent of any particular user), such a critter can't exist. Thus
these functions are here for the time being. */
void
sim_cb_printf (host_callback *p, const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
p->vprintf_filtered (p, fmt, ap);
va_end (ap);
}
void
sim_cb_eprintf (host_callback *p, const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
p->evprintf_filtered (p, fmt, ap);
va_end (ap);
}
int
cb_is_stdin (host_callback *cb, int fd)
{
return fdbad (cb, fd) ? 0 : fdmap (cb, fd) == 0;
}
int
cb_is_stdout (host_callback *cb, int fd)
{
return fdbad (cb, fd) ? 0 : fdmap (cb, fd) == 1;
}
int
cb_is_stderr (host_callback *cb, int fd)
{
return fdbad (cb, fd) ? 0 : fdmap (cb, fd) == 2;
}