Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: fix-socket-rea…
Fetching contributors…

Cannot retrieve contributors at this time

571 lines (439 sloc) 17.838 kb
/*
Copyright (C) 2001-2012, Parrot Foundation.
=head1 NAME
src/io/utilities.c - Utility and helper functions for the IO subsystem.
=head1 DESCRIPTION
Various helper and utility functions for the IO subsystem. Routines in this
file specifically help with common or shared operations and help add a layer
of intelligence between the IO API and the buffering API.
=head2 Functions
=over 4
=cut
*/
#include "parrot/parrot.h"
#include "io_private.h"
/* HEADERIZER HFILE: src/io/io_private.h */
/* HEADERIZER BEGIN: static */
/* HEADERIZER END: static */
/*
=item C<INTVAL Parrot_io_parse_open_flags(PARROT_INTERP, const STRING
*mode_str)>
Parses a Parrot string for file open mode flags (C<r> for read, C<w> for write,
C<a> for append, and C<p> for pipe) and returns the combined generic bit flags.
=cut
*/
PARROT_EXPORT
PARROT_WARN_UNUSED_RESULT
INTVAL
Parrot_io_parse_open_flags(PARROT_INTERP, ARGIN(const STRING *mode_str))
{
ASSERT_ARGS(Parrot_io_parse_open_flags)
INTVAL i, mode_len;
INTVAL flags = 0;
if (STRING_IS_NULL(mode_str))
return PIO_F_READ;
mode_len = Parrot_str_byte_length(interp, mode_str);
for (i = 0; i < mode_len; ++i) {
const INTVAL s = STRING_ord(interp, mode_str, i);
switch (s) {
case 'r':
flags |= PIO_F_READ;
break;
case 'w':
flags |= PIO_F_WRITE;
if (!(flags & PIO_F_APPEND)) /* don't truncate if appending */
flags |= PIO_F_TRUNC;
break;
case 'a':
flags |= PIO_F_APPEND;
flags |= PIO_F_WRITE;
if ((flags & PIO_F_TRUNC)) /* don't truncate if appending */
flags &= ~PIO_F_TRUNC;
break;
case 'p':
flags |= PIO_F_PIPE;
break;
case 'b':
flags |= PIO_F_BINARY;
break;
default:
break;
}
}
return flags;
}
/*
=item C<PMC * io_get_new_filehandle(PARROT_INTERP)>
Get a new FileHandle PMC, or whatever is the HLL-mapped alternative.
=item C<PMC * io_get_new_socket(PARROT_INTERP)>
Get a new Socket PMC, or whatever is the HLL-mapped alternative.
=item C<STRING * io_get_new_empty_string(PARROT_INTERP, const STR_VTABLE
*encoding, INTVAL char_length, INTVAL byte_length)>
Allocate a new, empty STRING header with some space pre-allocated to hold
incoming data.
=cut
*/
PARROT_CANNOT_RETURN_NULL
PARROT_WARN_UNUSED_RESULT
PMC *
io_get_new_filehandle(PARROT_INTERP)
{
ASSERT_ARGS(io_get_new_filehandle)
const INTVAL typenum = Parrot_hll_get_ctx_HLL_type(interp,
enum_class_FileHandle);
return Parrot_pmc_new(interp, typenum);
}
PARROT_CANNOT_RETURN_NULL
PARROT_WARN_UNUSED_RESULT
PMC *
io_get_new_socket(PARROT_INTERP)
{
ASSERT_ARGS(io_get_new_socket)
const INTVAL typenum = Parrot_hll_get_ctx_HLL_type(interp,
enum_class_Socket);
return Parrot_pmc_new(interp, typenum);
}
PARROT_CANNOT_RETURN_NULL
PARROT_WARN_UNUSED_RESULT
STRING *
io_get_new_empty_string(PARROT_INTERP, ARGIN_NULLOK(const STR_VTABLE *encoding),
INTVAL char_length, INTVAL byte_length)
{
ASSERT_ARGS(io_get_new_empty_string)
STRING * result;
/* Round up length to unit size of encoding */
if (char_length != -1 && encoding->bytes_per_unit > 1)
byte_length = (byte_length + encoding->bytes_per_unit - 1) &
~(encoding->bytes_per_unit - 1);
if (byte_length < PIO_STRING_BUFFER_MINSIZE)
byte_length = PIO_STRING_BUFFER_MINSIZE;
result = Parrot_str_new_noinit(interp, byte_length);
result->encoding = encoding;
result->hashval = 0;
return result;
}
/*
=item C<void io_verify_is_open_for(PARROT_INTERP, PMC *handle, const IO_VTABLE
*vtable, INTVAL flags)>
Verify that the given C<handle> is open for the operation(s) specified in
C<flags>. C<flags> can be one of C<PIO_F_READ> or C<PIO_F_WRITE>. This
function throws an exception if this handle is closed or if it is not open
for the specified mode.
There is an exception that certain types are flagged C<PIO_VF_ALWAYS_READABLE>
on the vtable. Those types are always readable, so take that into account.
=cut
*/
void
io_verify_is_open_for(PARROT_INTERP, ARGIN(PMC *handle),
ARGIN(const IO_VTABLE *vtable), INTVAL flags)
{
ASSERT_ARGS(io_verify_is_open_for)
/* Some types like StringHandle are always readable, even if only opened
in 'w' mode or when closed. Several parts of the build, test suite and
libraries depend on this. */
if (vtable->flags & PIO_VF_AWAYS_READABLE) {
if (flags == PIO_F_READ)
return;
else
flags &= ~PIO_F_READ;
}
if (Parrot_io_is_closed(interp, handle))
Parrot_ex_throw_from_c_args(interp, NULL, EXCEPTION_PIO_ERROR,
"IO PMC %s is not open", vtable->name);
if ((vtable->get_flags(interp, handle) & flags) == 0)
Parrot_ex_throw_from_c_args(interp, NULL, EXCEPTION_PIO_ERROR,
"IO PMC %s is not in mode %d", vtable->name, flags);
}
/*
=item C<IO_BUFFER * io_verify_has_read_buffer(PARROT_INTERP, PMC *handle, const
IO_VTABLE *vtable, INTVAL flags)>
Verify that the given C<handle> has a read buffer attached. If not, allocate
one.
=cut
*/
PARROT_WARN_UNUSED_RESULT
PARROT_CANNOT_RETURN_NULL
IO_BUFFER *
io_verify_has_read_buffer(PARROT_INTERP, ARGIN(PMC *handle),
ARGIN(const IO_VTABLE *vtable), INTVAL flags)
{
ASSERT_ARGS(io_verify_has_read_buffer)
IO_BUFFER * buffer = IO_GET_READ_BUFFER(interp, handle);
if (!buffer) {
const STR_VTABLE * encoding = vtable->get_encoding(interp, handle);
if (encoding == NULL)
encoding = Parrot_platform_encoding_ptr;
buffer = Parrot_io_buffer_allocate(interp, handle, flags, encoding, BUFFER_SIZE_ANY);
VTABLE_set_pointer_keyed_int(interp, handle, IO_PTR_IDX_READ_BUFFER, buffer);
}
PARROT_ASSERT(buffer);
if (flags != BUFFER_FLAGS_ANY)
buffer->flags = flags;
return buffer;
}
/*
=item C<STRING * io_verify_string_encoding(PARROT_INTERP, PMC *handle, const
IO_VTABLE *vtable, STRING *s, INTVAL flags)>
Verify that the given string C<s> has a suitable encoding for use with
C<handle>. If not, re-encode the string to be compatible. Return a string that
is compatible with C<handle>.
=cut
*/
PARROT_CANNOT_RETURN_NULL
PARROT_WARN_UNUSED_RESULT
STRING *
io_verify_string_encoding(PARROT_INTERP, ARGIN(PMC *handle),
ARGIN(const IO_VTABLE *vtable), ARGIN(STRING *s), INTVAL flags)
{
ASSERT_ARGS(io_verify_string_encoding)
const STR_VTABLE * const encoding = io_get_encoding(interp, handle, vtable, flags);
/* If we still don't have an encoding or if we don't need to do any
converting, we're good. Return. */
if (encoding == NULL || encoding == s->encoding || encoding == Parrot_binary_encoding_ptr)
return s;
/* Else, convert to the necessary encoding */
return encoding->to_encoding(interp, s);
}
/*
=item C<STRING * io_read_encoded_string(PARROT_INTERP, PMC *handle, const
IO_VTABLE *vtable, IO_BUFFER *buffer, const STR_VTABLE *encoding, size_t
char_length)>
Read a STRING from the handle with the given number of bytes, assuming the
handle is open and flagged PIO_F_READ. Perform the necessary shenanigans to
make sure the STRING contains complete codepoints for multibyte strings.
This requires a non-null, non-zero read buffer.
=cut
*/
PARROT_CANNOT_RETURN_NULL
PARROT_WARN_UNUSED_RESULT
STRING *
io_read_encoded_string(PARROT_INTERP, ARGMOD(PMC *handle),
ARGIN(const IO_VTABLE *vtable), ARGMOD(IO_BUFFER *buffer),
ARGIN_NULLOK(const STR_VTABLE *encoding), size_t char_length)
{
ASSERT_ARGS(io_read_encoded_string)
STRING * const s = Parrot_gc_new_string_header(interp, 0);
size_t total_bytes_read = 0;
Parrot_String_Bounds bounds;
size_t bytes_to_read = 0;
s->bufused = 0;
s->strlen = 0;
if (encoding == NULL)
encoding = io_get_encoding(interp, handle, vtable, PIO_F_READ);
s->encoding = encoding;
PARROT_ASSERT(s->encoding);
/* When we have a request with PIO_READ_SIZE_ANY, we just want a big chunk
of data. Fill the buffer up as full as it gets and read it out. */
if (char_length == PIO_READ_SIZE_ANY) {
/* FIXME: checking against encoding->max_bytes_per_codepoint is a
bad idea - we never might get that many bytes
*/
if (BUFFER_USED_SIZE(buffer) < encoding->max_bytes_per_codepoint)
Parrot_io_buffer_fill(interp, buffer, handle, vtable);
bytes_to_read = io_buffer_find_num_characters(interp, buffer,
handle, vtable, encoding, &bounds,
BUFFER_USED_SIZE(buffer));
if (bytes_to_read != 0)
io_read_chars_append_string(interp, s, handle, vtable, buffer, bytes_to_read);
return s;
}
/* Otherwise, we have a specific number of characters we're trying to get.
Loop until we read them all. */
while (1) {
bytes_to_read = io_buffer_find_num_characters(interp, buffer, handle,
vtable, encoding, &bounds, char_length);
/* Buffer is empty, so we're at EOF */
if (bytes_to_read == 0)
break;
/* Append buffer to result */
io_read_chars_append_string(interp, s, handle, vtable, buffer, bytes_to_read);
total_bytes_read += bytes_to_read;
if (((size_t)bounds.chars) == char_length)
break;
/* We weren't able to read so many characters at once. Count the ones
we've already gotten and continue */
char_length -= bounds.chars;
PARROT_ASSERT(char_length > 0);
/* Some types, like Socket, don't want to be read more than once in a
single request because recv can hang waiting for data. In those
cases, break out of the loop early. */
/* FIXME - buffer-raw_reads is gone
if ((vtable->flags & PIO_VF_MULTI_READABLE) == 0 && buffer->raw_reads > raw_reads)
break; */
}
return s;
}
/*
=item C<void io_read_chars_append_string(PARROT_INTERP, STRING * s, PMC *handle,
const IO_VTABLE *vtable, IO_BUFFER *buffer, size_t byte_length)>
Read characters out of the buffer and append them to the end of the existing
STRING. The STRING should be in "edit" mode and should not be referenced
by anything outside the IO subsystem. The byte_length should be accurate,
The buffer should be prepared and scanned ahead of time to ensure the
number of bytes to be read is the number of bytes actually available for
reading. The value in C<adv_length>, if larger than C<byte_length> is the
actual number of bytes to advance the buffer by (the difference adv_length -
byte_length are characters which are discarded).
=cut
*/
void
io_read_chars_append_string(PARROT_INTERP, ARGMOD(STRING * s),
ARGMOD(PMC *handle), ARGIN(const IO_VTABLE *vtable),
ARGMOD_NULLOK(IO_BUFFER *buffer), size_t byte_length)
{
ASSERT_ARGS(io_read_chars_append_string)
const size_t alloc_size = s->bufused + byte_length;
size_t bytes_read = 0;
PARROT_ASSERT(s->encoding);
PARROT_ASSERT(byte_length > 0);
if (alloc_size > s->_buflen) {
if (s->strstart)
Parrot_gc_reallocate_string_storage(interp, s, alloc_size);
else
Parrot_gc_allocate_string_storage(interp, s, alloc_size);
}
if (buffer)
bytes_read = Parrot_io_buffer_read_b(interp, buffer, handle, vtable,
s->strstart + s->bufused, byte_length);
else
bytes_read = vtable->read_b(interp, handle, s->strstart + s->bufused,
byte_length);
PARROT_ASSERT(bytes_read <= byte_length);
s->bufused += byte_length;
vtable->adv_position(interp, handle, byte_length);
STRING_scan(interp, s);
}
/*
=item C<STRING * io_readline_encoded_string(PARROT_INTERP, PMC *handle, const
IO_VTABLE *vtable, IO_BUFFER *buffer, const STR_VTABLE *encoding, STRING * rs)>
Read a line, up to and including the terminator string rs from the
input handle.
If the terminator string cannot be found, read until EOF is reached.
This may block handles like sockets until the connection gets closed, which is
necessary to ensure we never return incomplete lines.
=cut
*/
PARROT_CANNOT_RETURN_NULL
PARROT_WARN_UNUSED_RESULT
STRING *
io_readline_encoded_string(PARROT_INTERP, ARGMOD(PMC *handle),
ARGIN(const IO_VTABLE *vtable), ARGMOD(IO_BUFFER *buffer),
ARGIN_NULLOK(const STR_VTABLE *encoding), ARGIN(STRING * rs))
{
ASSERT_ARGS(io_readline_encoded_string)
STRING * const s = Parrot_gc_new_string_header(interp, 0);
/* XXX: What do we do if encoding and rs->encoding don't agree? */
const size_t delim_size = STRING_byte_length(rs);
s->bufused = 0;
s->strlen = 0;
if (encoding == NULL)
encoding = io_get_encoding(interp, handle, vtable, PIO_F_READ);
s->encoding = encoding;
while (1) {
Parrot_String_Bounds bounds;
INTVAL have_delim = 0;
/* Look for terminator and get amount of complete character data */
const size_t bytes_to_read = io_buffer_find_string_marker(
interp, buffer, handle, vtable, encoding, &bounds, rs, &have_delim);
if (bytes_to_read != 0) {
/* Append buffer to result */
io_read_chars_append_string(interp, s, handle, vtable, buffer, bytes_to_read);
if (have_delim)
break;
}
/* XXX: Should we discard any partial characters so vtable->is_eof()
and Parrot_io_eof() (and thus the PMC method) will agree?
Imo keeping the partial characters is the right thing to do.
*/
if (vtable->is_eof(interp, handle))
break;
/* We haven't found the terminator yet, so get new input and try again */
Parrot_io_buffer_fill(interp, buffer, handle, vtable);
}
return s;
}
/*
=item C<const STR_VTABLE * io_get_encoding(PARROT_INTERP, PMC *handle, const
IO_VTABLE *vtable, INTVAL flags)>
Get the encoding of C<handle>. If C<handle> doesn't have an encoding specified
pick one that this suitable for the operation described in C<flags>. If
C<flags> is PIO_F_WRITE, return a binary encoding for general-purpose writes.
If C<flags> is PIO_F_READ, return the platform default encoding.
=cut
*/
PARROT_CANNOT_RETURN_NULL
PARROT_WARN_UNUSED_RESULT
const STR_VTABLE *
io_get_encoding(PARROT_INTERP, ARGMOD(PMC *handle), ARGIN(const IO_VTABLE *vtable), INTVAL flags)
{
ASSERT_ARGS(io_get_encoding)
const STR_VTABLE * const encoding = vtable->get_encoding(interp, handle);
if (encoding != NULL)
return encoding;
if (flags & PIO_F_WRITE)
return Parrot_binary_encoding_ptr;
if (flags & PIO_F_READ)
return Parrot_default_encoding_ptr;
return Parrot_default_encoding_ptr;
}
/*
=item C<void io_sync_buffers_for_read(PARROT_INTERP, PMC *handle, const
IO_VTABLE *vtable, IO_BUFFER *read_buffer, IO_BUFFER * write_buffer)>
Synchronize buffers for a read operation on a "rw" handle. In order to read
data from the handle, we must first flush the data in the write buffer (if
any) and update the read pointer to the correct location.
=cut
*/
void
io_sync_buffers_for_read(PARROT_INTERP, ARGMOD(PMC *handle),
ARGIN(const IO_VTABLE *vtable), ARGMOD_NULLOK(IO_BUFFER *read_buffer),
ARGMOD_NULLOK(IO_BUFFER * write_buffer))
{
ASSERT_ARGS(io_sync_buffers_for_read)
/* If we have a non-empty write buffer, we have to flush it first
and advance the cursor before we attempt to read. Otherwise we'll be
reading data from the buffer that has already been overwritten, from a
position several bytes before where we're supposed to be. */
if (write_buffer && !BUFFER_IS_EMPTY(write_buffer) && (vtable->flags & PIO_VF_SYNC_IO)) {
const size_t bytes_written = Parrot_io_buffer_flush(interp, write_buffer,
handle, vtable);
PARROT_ASSERT(BUFFER_IS_EMPTY(write_buffer));
Parrot_io_buffer_advance_position(interp, read_buffer, bytes_written);
}
}
/*
=item C<void io_sync_buffers_for_write(PARROT_INTERP, PMC *handle, const
IO_VTABLE *vtable, IO_BUFFER *read_buffer, IO_BUFFER * write_buffer)>
Synchronize buffers for a write operation on a "rw" handle. In order to write
data to the handle we need to reset the on-disk pointer by calculating how
far ahead the read_buffer has moved and resetting the pointer to the correct
location using C<seek>.
=cut
*/
void
io_sync_buffers_for_write(PARROT_INTERP, ARGMOD(PMC *handle),
ARGIN(const IO_VTABLE *vtable), ARGMOD_NULLOK(IO_BUFFER *read_buffer),
ARGMOD_NULLOK(IO_BUFFER * write_buffer))
{
ASSERT_ARGS(io_sync_buffers_for_write)
UNUSED(write_buffer);
/* If we have a read buffer, the on-disk file position is ahead of the
current cursor. We need to clear the read buffer and reset the on-disk
position to match what we expect it to be, so the written data goes
to the correct place */
if (read_buffer && !BUFFER_IS_EMPTY(read_buffer) && (vtable->flags & PIO_VF_SYNC_IO)) {
const size_t buffer_size = BUFFER_USED_SIZE(read_buffer);
Parrot_io_buffer_clear(interp, read_buffer);
vtable->seek(interp, handle, -(PIOOFF_T)buffer_size, SEEK_CUR);
}
}
/*
=back
=cut
*/
/*
* Local variables:
* c-file-style: "parrot"
* End:
* vim: expandtab shiftwidth=4 cinoptions='\:2=2' :
*/
Jump to Line
Something went wrong with that request. Please try again.