/*
Copyright (C) 2001-2014, Parrot Foundation.

=head1 NAME

src/platform/generic/io.c - UNIX IO utility functions

=head1 DESCRIPTION

This file implements unbuffered, low-level, UNIX-specific functionality.
"UNIX" is a generalization, it may be necessary to create separate OS-specific
functions for UNIX flavors.

These functions are not part of Parrot's API. Don't call them directly, call
the C<Parrot_io_*> functions in F<src/io/api.c> instead. Each platform defines
the standard set of macros, which call the correct functions for that platform.

=head2 References:

APitUE - W. Richard Stevens, AT&T SFIO, Perl 5 (Nick Ing-Simmons)

=head2 Functions

=over 4

=cut

*/

#include "parrot/parrot.h"
#include "../../io/io_private.h"

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h> /* for pipe() */

#define DEFAULT_OPEN_MODE S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH

#ifndef STDIN_FILENO
#  define STDIN_FILENO 0
#endif

#ifndef STDOUT_FILENO
#  define STDOUT_FILENO 1
#endif

#ifndef STDERR_FILENO
#  define STDERR_FILENO 2
#endif

/* HEADERIZER HFILE: none */

/* HEADERIZER BEGIN: static */
/* Don't modify between HEADERIZER BEGIN / HEADERIZER END.  Your changes will be lost. */

PARROT_CONST_FUNCTION
static int convert_flags_to_unix(const INTVAL flags);

#define ASSERT_ARGS_convert_flags_to_unix __attribute__unused__ int _ASSERT_ARGS_CHECK = (0)
/* Don't modify between HEADERIZER BEGIN / HEADERIZER END.  Your changes will be lost. */
/* HEADERIZER END: static */


/*

=item C<static int convert_flags_to_unix(const INTVAL flags)>

Returns a UNIX-specific interpretation of C<flags> suitable for passing
to C<open()> and C<fopen()> in C<Parrot_io_open_unix()>.

=cut

*/

PARROT_CONST_FUNCTION
static int
convert_flags_to_unix(const INTVAL flags)
{
    ASSERT_ARGS(convert_flags_to_unix)
    int oflags = 0;

    if ((flags & (PIO_F_WRITE | PIO_F_READ)) == (PIO_F_WRITE | PIO_F_READ)) {
        oflags |= O_RDWR | O_CREAT;
    }
    else if (flags & PIO_F_WRITE) {
        oflags |= O_WRONLY | O_CREAT;
    }
    else if (flags & PIO_F_READ) {
        oflags |= O_RDONLY;
    }

    if (flags & PIO_F_APPEND) {
        oflags |= O_APPEND;
    }
    else if (flags & PIO_F_TRUNC) {
        oflags |= O_TRUNC;
    }
    return oflags;
}

/*

=item C<PIOHANDLE Parrot_io_internal_std_os_handle(PARROT_INTERP, const INTVAL
fileno)>

Returns a standard file handle.

=cut

*/

PIOHANDLE
Parrot_io_internal_std_os_handle(SHIM_INTERP, const INTVAL fileno)
{
    PIOHANDLE os_handle;

    switch (fileno) {
      case PIO_STDIN_FILENO:
      default:
        os_handle = STDIN_FILENO;
        break;
      case PIO_STDOUT_FILENO:
        os_handle = STDOUT_FILENO;
        break;
      case PIO_STDERR_FILENO:
        os_handle = STDERR_FILENO;
        break;
    }

    return os_handle;
}

/*

=item C<PIOHANDLE Parrot_io_internal_open(PARROT_INTERP, const STRING * const
path, const INTVAL flags)>

Opens a string C<path>. C<flags> is a bitwise C<or> combination of C<PIO_F_*>
flag values.

=cut

*/

PARROT_WARN_UNUSED_RESULT
PIOHANDLE
Parrot_io_internal_open(PARROT_INTERP, ARGIN(const STRING * const path), const INTVAL flags)
{
    struct stat  buf;
    PIOHANDLE    fd;

    const int oflags   = convert_flags_to_unix(flags);
    const char * const spath = Parrot_str_to_platform_cstring(interp, path);

    while ((fd = open(spath, oflags, DEFAULT_OPEN_MODE)) < 0
    &&      errno == EINTR)
        errno = 0;

    Parrot_str_free_cstring((char*)spath);

    if (fd < 0)
        return PIO_INVALID_HANDLE;

    /* Don't open directories */
    if (fstat(fd, &buf) == -1
    ||  S_ISDIR(buf.st_mode)) {
        close(fd);
        return PIO_INVALID_HANDLE;
    }

    return fd;
}

/*

=item C<PIOHANDLE Parrot_io_internal_dup(PARROT_INTERP, const PIOHANDLE handle)>

Duplicates file handle C<handle>.

=cut

*/

PARROT_WARN_UNUSED_RESULT
PIOHANDLE
Parrot_io_internal_dup(SHIM_INTERP, const PIOHANDLE handle)
{
    return dup(handle);
}

/*

=item C<INTVAL Parrot_io_internal_async(PARROT_INTERP, PMC * const pmc, const
INTVAL async)>

Sets a handle C<*pmc> to blocking or non-blocking mode

TODO: Change this function signature to take the PIOHANDLE instead of having
to query it from the pmc.

=cut

*/

PARROT_EXPORT
PARROT_WARN_UNUSED_RESULT
INTVAL
Parrot_io_internal_async(PARROT_INTERP, ARGMOD(PMC * const pmc), const INTVAL async)
{
    int rflags;
    PIOHANDLE file_descriptor;

    if (Parrot_io_is_closed(interp, pmc))
        return 0;

#if defined(__linux__)
    file_descriptor = Parrot_io_get_os_handle(interp, pmc);

    if ((rflags = fcntl(file_descriptor, F_GETFL, 0)) >= 0) {
        if (async)
            rflags |= O_ASYNC;
        else
            rflags &= ~O_ASYNC;
        if ((rflags = fcntl(file_descriptor, F_SETFL, rflags)) == 0) {
            if (async)
               Parrot_io_set_flags(interp, pmc, Parrot_io_get_flags(interp, pmc) | PIO_F_ASYNC);
            else
               Parrot_io_set_flags(interp, pmc, Parrot_io_get_flags(interp, pmc) & ~PIO_F_ASYNC);
        }
        return rflags;
    }
#else
    UNUSED(async)
    Parrot_ex_throw_from_c_noargs(interp, EXCEPTION_PIO_NOT_IMPLEMENTED,
        "Async support not available");
#endif
    return -1;
}

/*

=item C<INTVAL Parrot_io_internal_close(PARROT_INTERP, const PIOHANDLE
file_descriptor)>

Closes C<*io>'s file descriptor.

=cut

*/

INTVAL
Parrot_io_internal_close(SHIM_INTERP, const PIOHANDLE file_descriptor)
{
    INTVAL result = 0;

    /* BSD and Solaris need explicit fsync() */
    if (file_descriptor >= 0) {
        fsync(file_descriptor);

        if (close(file_descriptor) != 0)
            result = errno;
    }

    return result;
}


/*

=item C<INTVAL Parrot_io_internal_is_tty(PARROT_INTERP, const PIOHANDLE fd)>

Returns a boolean value indicating whether C<fd> is a console/tty.

=cut

*/

PARROT_WARN_UNUSED_RESULT
INTVAL
Parrot_io_internal_is_tty(SHIM_INTERP, const PIOHANDLE fd)
{
    return isatty(fd);
}

/*

=item C<INTVAL Parrot_io_internal_getblksize(const PIOHANDLE fd)>

Various ways of determining block size.

If passed a file descriptor then C<fstat()> and the C<stat> buffer are
used if available.

If called without an argument then the C<BLKSIZE> constant is returned
if it was available at compile time, otherwise C<PIO_BLKSIZE> is returned.

=cut

*/

PARROT_CONST_FUNCTION
INTVAL
Parrot_io_internal_getblksize(const PIOHANDLE fd)
{
    if (fd >= 0) {
        /* Try to get the block size of a regular file */
#if 0
        /*
         * Is it even worth adding non-portable code here
         * or should we just estimate a nice buffer size?
         * Some systems have st_blksize, some don't.
         */
        {
            struct stat sbuf;
            int err;
            err = fstat(fd, &sbuf);
            if (err == 0) {
                return sbuf.st_blksize;
            }
        }
#endif
    }
    /* Try to determine it from general means. */
#ifdef BLKSIZE
    return BLKSIZE;
#else
    return PIO_BLKSIZE;
#endif
}

/*

=item C<INTVAL Parrot_io_internal_flush(PARROT_INTERP, const PIOHANDLE
os_handle)>

At lowest layer all we can do for C<flush> is to ask the kernel to
C<sync()>.

=cut

*/

INTVAL
Parrot_io_internal_flush(SHIM_INTERP, const PIOHANDLE os_handle)
{
    return fsync(os_handle);
}

/*

=item C<size_t Parrot_io_internal_read(PARROT_INTERP, PIOHANDLE const os_handle,
char * const buf, const size_t len)>

Calls C<read()> to return up to C<len> bytes in the memory starting at
C<buffer>.

=cut

*/

size_t
Parrot_io_internal_read(PARROT_INTERP, PIOHANDLE const os_handle, ARGMOD(char * const buf),
                        const size_t len)
{

    for (;;) {
        const int bytes = read(os_handle, buf, len);

        if (bytes >= 0)
            return bytes;

        if (errno != EINTR)
            Parrot_ex_throw_from_c_args(interp, NULL, EXCEPTION_PIO_ERROR,
                    "Read error: %s", strerror(errno));
    }
}

/*

=item C<size_t Parrot_io_internal_write(PARROT_INTERP, const PIOHANDLE
os_handle, const char * const buf, const size_t len)>

Calls C<write()> to write C<len> bytes from the memory starting at
C<buffer> to the file descriptor in C<*io>.

=cut

*/

size_t
Parrot_io_internal_write(PARROT_INTERP, const PIOHANDLE os_handle,
        ARGIN(const char * const buf), const size_t len)
{
    const char *ptr      = buf;
    size_t      to_write = len;
    size_t      written  = 0;

    while (to_write > 0) {
        const int count = write(os_handle, ptr, to_write);

        if (count >= 0) {
            ptr      += count;
            to_write -= count;
            written  += count;
        }
        else {
            switch (errno) {
            case EINTR:
                continue;
#ifdef EAGAIN
            case EAGAIN:
                break;
#endif
            default:
                Parrot_ex_throw_from_c_args(interp, NULL, EXCEPTION_PIO_ERROR,
                        "Write error: %s", strerror(errno));
            }
        }
    }

    return written;
}

/*

=item C<PIOOFF_T Parrot_io_internal_seek(PARROT_INTERP, PIOHANDLE const
os_handle, const PIOOFF_T offset, const INTVAL whence)>

Hard seek.

Calls C<lseek()> to advance the read/write position on C<*io>'s file
descriptor to C<offset> bytes from the location indicated by C<whence>.

=cut

*/

PIOOFF_T
Parrot_io_internal_seek(SHIM_INTERP, PIOHANDLE const os_handle, const PIOOFF_T offset,
                        const INTVAL whence)
{
    const PIOOFF_T pos = lseek(os_handle, offset, whence);
    return pos;
}

/*

=item C<PIOOFF_T Parrot_io_internal_tell(PARROT_INTERP, PIOHANDLE const
os_handle)>

Returns the current read/write position on C<*io>'s file descriptor.

=cut

*/

PIOOFF_T
Parrot_io_internal_tell(SHIM_INTERP, PIOHANDLE const os_handle)
{
    const PIOOFF_T pos = lseek(os_handle, (PIOOFF_T)0, SEEK_CUR);
    return pos;
}

/*

=item C<PIOHANDLE Parrot_io_internal_open_pipe(PARROT_INTERP, const STRING *
const command, const INTVAL flags, INTVAL *pid_out)>

Very limited C<exec> for now.

=cut

*/

PARROT_WARN_UNUSED_RESULT
PIOHANDLE
Parrot_io_internal_open_pipe(PARROT_INTERP, ARGIN(const STRING * const command),
                             const INTVAL flags, ARGOUT(INTVAL *pid_out))
{
    PIOHANDLE handles[3];

    if (flags & PIO_F_READ) {
        *pid_out = Parrot_proc_exec(interp, (STRING*)command, PARROT_EXEC_STDOUT,
                        handles);
        return handles[1];
    }
    else {
        *pid_out = Parrot_proc_exec(interp, (STRING*)command, PARROT_EXEC_STDIN,
                        handles);
        return handles[0];
    }
}

/*

=item C<INTVAL Parrot_io_internal_pipe(PARROT_INTERP, PIOHANDLE *reader,
PIOHANDLE *writer)>

Uses C<pipe()> to create a matched pair of pipe fds.  Returns 0 on success, -1
on failure.

=cut

*/

PARROT_WARN_UNUSED_RESULT
PARROT_CAN_RETURN_NULL
INTVAL
Parrot_io_internal_pipe(SHIM_INTERP, ARGMOD(PIOHANDLE *reader), ARGMOD(PIOHANDLE *writer))
{
    int fds[2];
    const int rv = pipe(fds);

    if (rv >= 0) {
        *reader = fds[0];
        *writer = fds[1];
    }
    return rv;
}

/*

=back

=head1 SEE ALSO

F<src/io/api.c>,
F<src/io/io_private.h>,
F<include/parrot/io.h>.

=cut

*/


/*
 * Local variables:
 *   c-file-style: "parrot"
 * End:
 * vim: expandtab shiftwidth=4 cinoptions='\:2=2' :
 */
