summaryrefslogtreecommitdiff
path: root/ext/io/wait/wait.c
blob: f7a7508eeb58956f2337baac6932b5914da0d84f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/**********************************************************************

  io/wait.c -

  $Author$
  created at: Tue Aug 28 09:08:06 JST 2001

  All the files in this distribution are covered under the Ruby's
  license (see the file COPYING).

**********************************************************************/

#include "ruby.h"
#include "ruby/io.h"

#include <sys/types.h>
#if defined(HAVE_UNISTD_H) && (defined(__sun))
#include <unistd.h>
#endif
#if defined(HAVE_SYS_IOCTL_H)
#include <sys/ioctl.h>
#endif
#if defined(FIONREAD_HEADER)
#include FIONREAD_HEADER
#endif

#ifdef HAVE_RB_W32_IOCTLSOCKET
#define ioctl ioctlsocket
#define ioctl_arg u_long
#define ioctl_arg2num(i) ULONG2NUM(i)
#else
#define ioctl_arg int
#define ioctl_arg2num(i) INT2NUM(i)
#endif

#ifdef HAVE_RB_W32_IS_SOCKET
#define FIONREAD_POSSIBLE_P(fd) rb_w32_is_socket(fd)
#else
#define FIONREAD_POSSIBLE_P(fd) ((void)(fd),Qtrue)
#endif

static VALUE io_ready_p _((VALUE io));
static VALUE io_wait_readable _((int argc, VALUE *argv, VALUE io));
static VALUE io_wait_writable _((int argc, VALUE *argv, VALUE io));
void Init_wait _((void));

static struct timeval *
get_timeout(int argc, VALUE *argv, struct timeval *timerec)
{
    VALUE timeout = Qnil;
    rb_check_arity(argc, 0, 1);
    if (!argc || NIL_P(timeout = argv[0])) {
	return NULL;
    }
    else {
	*timerec = rb_time_interval(timeout);
	return timerec;
    }
}

static int
wait_for_single_fd(rb_io_t *fptr, int events, struct timeval *tv)
{
    int i = rb_wait_for_single_fd(fptr->fd, events, tv);
    if (i < 0)
	rb_sys_fail(0);
    rb_io_check_closed(fptr);
    return (i & events);
}

/*
 * call-seq:
 *   io.nread -> int
 *
 * Returns number of bytes that can be read without blocking.
 * Returns zero if no information available.
 */

static VALUE
io_nread(VALUE io)
{
    rb_io_t *fptr;
    int len;
    ioctl_arg n;

    GetOpenFile(io, fptr);
    rb_io_check_readable(fptr);
    len = rb_io_read_pending(fptr);
    if (len > 0) return INT2FIX(len);
    if (!FIONREAD_POSSIBLE_P(fptr->fd)) return INT2FIX(0);
    if (ioctl(fptr->fd, FIONREAD, &n)) return INT2FIX(0);
    if (n > 0) return ioctl_arg2num(n);
    return INT2FIX(0);
}

/*
 * call-seq:
 *   io.ready? -> true, false or nil
 *
 * Returns true if input available without blocking, or false.
 * Returns nil if no information available.
 */

static VALUE
io_ready_p(VALUE io)
{
    rb_io_t *fptr;
    struct timeval tv = {0, 0};

    GetOpenFile(io, fptr);
    rb_io_check_readable(fptr);
    if (rb_io_read_pending(fptr)) return Qtrue;
    if (wait_for_single_fd(fptr, RB_WAITFD_IN, &tv))
	return Qtrue;
    return Qfalse;
}

/*
 * call-seq:
 *   io.wait_readable          -> IO, true or nil
 *   io.wait_readable(timeout) -> IO, true or nil
 *
 * Waits until IO is readable without blocking and returns +self+, or
 * +nil+ when times out.
 * Returns +true+ immediately when buffered data is available.
 */

static VALUE
io_wait_readable(int argc, VALUE *argv, VALUE io)
{
    rb_io_t *fptr;
    struct timeval timerec;
    struct timeval *tv;

    GetOpenFile(io, fptr);
    rb_io_check_readable(fptr);
    tv = get_timeout(argc, argv, &timerec);
    if (rb_io_read_pending(fptr)) return Qtrue;
    if (wait_for_single_fd(fptr, RB_WAITFD_IN, tv)) {
	return io;
    }
    return Qnil;
}

/*
 * call-seq:
 *   io.wait_writable          -> IO
 *   io.wait_writable(timeout) -> IO or nil
 *
 * Waits until IO is writable without blocking and returns +self+ or
 * +nil+ when times out.
 */
static VALUE
io_wait_writable(int argc, VALUE *argv, VALUE io)
{
    rb_io_t *fptr;
    struct timeval timerec;
    struct timeval *tv;

    GetOpenFile(io, fptr);
    rb_io_check_writable(fptr);
    tv = get_timeout(argc, argv, &timerec);
    if (wait_for_single_fd(fptr, RB_WAITFD_OUT, tv)) {
	return io;
    }
    return Qnil;
}

static int
wait_mode_sym(VALUE mode)
{
    if (mode == ID2SYM(rb_intern("r"))) {
	return RB_WAITFD_IN;
    }
    if (mode == ID2SYM(rb_intern("read"))) {
	return RB_WAITFD_IN;
    }
    if (mode == ID2SYM(rb_intern("readable"))) {
	return RB_WAITFD_IN;
    }
    if (mode == ID2SYM(rb_intern("w"))) {
	return RB_WAITFD_OUT;
    }
    if (mode == ID2SYM(rb_intern("write"))) {
	return RB_WAITFD_OUT;
    }
    if (mode == ID2SYM(rb_intern("writable"))) {
	return RB_WAITFD_OUT;
    }
    if (mode == ID2SYM(rb_intern("rw"))) {
	return RB_WAITFD_IN|RB_WAITFD_OUT;
    }
    if (mode == ID2SYM(rb_intern("read_write"))) {
	return RB_WAITFD_IN|RB_WAITFD_OUT;
    }
    if (mode == ID2SYM(rb_intern("readable_writable"))) {
	return RB_WAITFD_IN|RB_WAITFD_OUT;
    }
    rb_raise(rb_eArgError, "unsupported mode: %"PRIsVALUE, mode);
    return 0;
}

/*
 * call-seq:
 *   io.wait(timeout = nil, mode = :read) -> IO, true or nil
 *
 * Waits until IO is readable or writable without blocking and returns
 * +self+, or +nil+ when times out.
 * Returns +true+ immediately when buffered data is available.
 * Optional parameter +mode+ is one of +:read+, +:write+, or
 * +:read_write+.
 */

static VALUE
io_wait_readwrite(int argc, VALUE *argv, VALUE io)
{
    rb_io_t *fptr;
    struct timeval timerec;
    struct timeval *tv = NULL;
    int event = 0;
    int i;

    GetOpenFile(io, fptr);
    for (i = 0; i < argc; ++i) {
	if (SYMBOL_P(argv[i])) {
	    event |= wait_mode_sym(argv[i]);
	}
	else {
	    *(tv = &timerec) = rb_time_interval(argv[i]);
	}
    }
    /* rb_time_interval() and might_mode() might convert the argument */
    rb_io_check_closed(fptr);
    if (!event) event = RB_WAITFD_IN;
    if ((event & RB_WAITFD_IN) && rb_io_read_pending(fptr))
	return Qtrue;
    if (wait_for_single_fd(fptr, event, tv))
	return io;
    return Qnil;
}

/*
 * IO wait methods
 */

void
Init_wait(void)
{
    rb_define_method(rb_cIO, "nread", io_nread, 0);
    rb_define_method(rb_cIO, "ready?", io_ready_p, 0);
    rb_define_method(rb_cIO, "wait", io_wait_readwrite, -1);
    rb_define_method(rb_cIO, "wait_readable", io_wait_readable, -1);
    rb_define_method(rb_cIO, "wait_writable", io_wait_writable, -1);
}