HelenOS sources

root/uspace/lib/riff/src/chunk.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. riff_wopen
  2. riff_wclose
  3. riff_write_uint32
  4. riff_wchunk_start
  5. riff_wchunk_end
  6. riff_write
  7. riff_ropen
  8. riff_rclose
  9. riff_read_uint32
  10. riff_rchunk_start
  11. riff_rchunk_match
  12. riff_rchunk_list_match
  13. riff_rchunk_seek
  14. riff_rchunk_size
  15. riff_rchunk_get_end
  16. riff_rchunk_get_ndpos
  17. riff_rchunk_end
  18. riff_read

/*
 * Copyright (c) 2015 Jiri Svoboda
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup libriff
 * @{
 */
/**
 * @file RIFF chunk.
 */

#include <assert.h>
#include <byteorder.h>
#include <errno.h>
#include <macros.h>
#include <riff/chunk.h>
#include <stdbool.h>
#include <stdlib.h>

/** Open RIFF file for writing
 *
 * @param fname File name
 * @param rrw   Place to store pointer to RIFF writer
 *
 * @return EOK on success, ENOMEM if out of memory, EIO if failed to open
 *         file.
 */
errno_t riff_wopen(const char *fname, riffw_t **rrw)
{
        riffw_t *rw;

        rw = calloc(1, sizeof(riffw_t));
        if (rw == NULL)
                return ENOMEM;

        rw->f = fopen(fname, "wb");
        if (rw->f == NULL) {
                free(rw);
                return EIO;
        }

        *rrw = rw;
        return EOK;
}

/** Close RIFF for writing.
 *
 * @param rw RIFF writer
 * @return EOK on success. On write error EIO is returned and RIFF writer
 *         is destroyed anyway.
 */
errno_t riff_wclose(riffw_t *rw)
{
        int rv;

        rv = fclose(rw->f);
        free(rw);

        return (rv == 0) ? EOK : EIO;
}

/** Write uint32_t value into RIFF file
 *
 * @param rw RIFF writer
 * @param v  Value
 * @return EOK on success, EIO on error.
 */
errno_t riff_write_uint32(riffw_t *rw, uint32_t v)
{
        uint32_t vle;

        vle = host2uint32_t_le(v);
        if (fwrite(&vle, 1, sizeof(vle), rw->f) < sizeof(vle))
                return EIO;

        return EOK;
}

/** Begin writing chunk.
 *
 * @param rw     RIFF writer
 * @param ckid   Chunk ID
 * @param wchunk Pointer to chunk structure to fill in
 *
 * @return EOK on success, EIO on write error
 */
errno_t riff_wchunk_start(riffw_t *rw, riff_ckid_t ckid, riff_wchunk_t *wchunk)
{
        long pos;
        errno_t rc;

        pos = ftell(rw->f);
        if (pos < 0)
                return EIO;

        wchunk->ckstart = pos + 2 * sizeof(uint32_t);

        rc = riff_write_uint32(rw, ckid);
        if (rc != EOK) {
                assert(rc == EIO);
                return EIO;
        }

        rc = riff_write_uint32(rw, 0);
        if (rc != EOK) {
                assert(rc == EIO);
                return EIO;
        }

        return EOK;
}

/** Finish writing chunk.
 *
 * @param rw     RIFF writer
 * @param wchunk Pointer to chunk structure
 *
 * @return EOK on success, EIO error.
 */
errno_t riff_wchunk_end(riffw_t *rw, riff_wchunk_t *wchunk)
{
        long pos;
        long cksize;
        uint8_t pad;
        size_t nw;
        errno_t rc;

        pos = ftell(rw->f);
        if (pos < 0)
                return EIO;

        cksize = pos - wchunk->ckstart;
        if (pos % 2 != 0) {
                ++pos;
                pad = 0;
                nw = fwrite(&pad, 1, sizeof(pad), rw->f);
                if (nw != sizeof(pad))
                        return EIO;
        }

        if (fseek(rw->f, wchunk->ckstart - 4, SEEK_SET) < 0)
                return EIO;

        rc = riff_write_uint32(rw, cksize);
        if (rc != EOK) {
                assert(rc == EIO);
                return EIO;
        }

        if (fseek(rw->f, pos, SEEK_SET) < 0)
                return EIO;

        return EOK;
}

/** Write data into RIFF file.
 *
 * @param rw    RIFF writer
 * @param data  Pointer to data
 * @param bytes Number of bytes to write
 *
 * @return EOK on success, EIO on error.
 */
errno_t riff_write(riffw_t *rw, void *data, size_t bytes)
{
        size_t nw;

        nw = fwrite(data, 1, bytes, rw->f);
        if (nw != bytes)
                return EIO;

        return EOK;
}

/** Open RIFF file for reading.
 *
 * @param fname File name
 * @param riffck Place to store root (RIFF) chunk
 * @param rrr   Place to store pointer to RIFF reader
 *
 * @return EOK on success, ENOMEM if out of memory, EIO if failed to open
 *         file..
 */
errno_t riff_ropen(const char *fname, riff_rchunk_t *riffck, riffr_t **rrr)
{
        riffr_t *rr;
        riff_rchunk_t fchunk;
        long fsize;
        errno_t rc;
        int rv;

        rr = calloc(1, sizeof(riffr_t));
        if (rr == NULL) {
                rc = ENOMEM;
                goto error;
        }

        rr->pos = 0;

        rr->f = fopen(fname, "rb");
        if (rr->f == NULL) {
                rc = EIO;
                goto error;
        }

        rv = fseek(rr->f, 0, SEEK_END);
        if (rv < 0) {
                rc = EIO;
                goto error;
        }

        fsize = ftell(rr->f);
        if (fsize < 0) {
                rc = EIO;
                goto error;
        }

        rv = fseek(rr->f, 0, SEEK_SET);
        if (rv < 0) {
                rc = EIO;
                goto error;
        }

        fchunk.riffr = rr;
        fchunk.ckstart = 0;
        fchunk.cksize = fsize;

        rc = riff_rchunk_start(&fchunk, riffck);
        if (rc != EOK)
                goto error;

        *rrr = rr;
        return EOK;
error:
        if (rr != NULL && rr->f != NULL)
                fclose(rr->f);
        free(rr);
        return rc;
}

/** Close RIFF for reading.
 *
 * @param rr RIFF reader
 * @return EOK on success, EIO on error.
 */
errno_t riff_rclose(riffr_t *rr)
{
        errno_t rc;

        rc = fclose(rr->f);
        free(rr);
        return rc == 0 ? EOK : EIO;
}

/** Read uint32_t from RIFF file.
 *
 * @param rchunk RIFF chunk
 * @param v  Place to store value
 * @return EOK on success, ELIMIT if at the end of parent chunk, EIO on error.
 */
errno_t riff_read_uint32(riff_rchunk_t *rchunk, uint32_t *v)
{
        uint32_t vle;
        errno_t rc;
        size_t nread;

        rc = riff_read(rchunk, &vle, sizeof(vle), &nread);
        if (rc != EOK)
                return rc;

        if (nread != sizeof(vle))
                return ELIMIT;

        *v = uint32_t_le2host(vle);
        return EOK;
}

/** Start reading RIFF chunk.
 *
 * @param parent Parent chunk
 * @param rchunk Pointer to chunk structure to fill in
 *
 * @return EOK on success, ELIMIT if at the end of parent chunk,
 *         EIO on error.
 */
errno_t riff_rchunk_start(riff_rchunk_t *parent, riff_rchunk_t *rchunk)
{
        errno_t rc;

        rchunk->riffr = parent->riffr;
        rchunk->ckstart = parent->riffr->pos + 8;
        rc = riff_read_uint32(parent, &rchunk->ckid);
        if (rc != EOK)
                goto error;
        rc = riff_read_uint32(parent, &rchunk->cksize);
        if (rc != EOK)
                goto error;

        return EOK;
error:
        return rc;
}

/** Find and start reading RIFF chunk of with specific chunk ID.
 * Other types of chunks are skipped.
 *
 * @param parent Parent chunk
 * @param rchunk Pointer to chunk structure to fill in
 *
 * @return EOK on success, ENOENT chunk was not found and end was reached
 *         EIO on error.
 */
errno_t riff_rchunk_match(riff_rchunk_t *parent, riff_ckid_t ckid,
    riff_rchunk_t *rchunk)
{
        errno_t rc;

        while (true) {
                rc = riff_rchunk_start(parent, rchunk);
                if (rc == ELIMIT)
                        return ENOENT;
                if (rc != EOK)
                        return rc;

                if (rchunk->ckid == ckid)
                        break;

                rc = riff_rchunk_end(rchunk);
                if (rc != EOK)
                        return rc;
        }

        return EOK;
}

/** Find and start reading RIFF LIST chunk of specified type.
 * Other chunks or LIST chunks of other type are skipped.
 *
 * @param parent Parent chunk
 * @param rchunk Pointer to chunk structure to fill in
 *
 * @return EOK on success, ENOENT chunk was not found and end was reached
 *         EIO on error.
 */
errno_t riff_rchunk_list_match(riff_rchunk_t *parent, riff_ltype_t ltype,
    riff_rchunk_t *rchunk)
{
        errno_t rc;
        riff_ltype_t rltype;

        while (true) {
                rc = riff_rchunk_match(parent, CKID_LIST, rchunk);
                if (rc == ELIMIT)
                        return ENOENT;
                if (rc != EOK)
                        return rc;

                rc = riff_read_uint32(parent, &rltype);
                if (rc != EOK)
                        return rc;

                if (rltype == ltype)
                        break;

                rc = riff_rchunk_end(rchunk);
                if (rc != EOK)
                        return rc;
        }

        return EOK;
}

/** Seek to position in chunk.
 *
 * @param rchunk RIFF chunk
 * @param offset Offset
 * @param whence SEEK_SET, SEEK_CUR or SEEK_END
 * @return EOK on success or an error code
 */
errno_t riff_rchunk_seek(riff_rchunk_t *rchunk, long offset, int whence)
{
        long dest;
        int rv;

        switch (whence) {
        case SEEK_SET:
                dest = rchunk->ckstart + offset;
                break;
        case SEEK_END:
                dest = rchunk->ckstart + rchunk->cksize + offset;
                break;
        case SEEK_CUR:
                dest = rchunk->riffr->pos + offset;
                break;
        default:
                return EINVAL;
        }

        if (dest < rchunk->ckstart || (unsigned long) dest >
            (unsigned long) rchunk->ckstart + rchunk->cksize) {
                return ELIMIT;
        }

        rv = fseek(rchunk->riffr->f, dest, SEEK_SET);
        if (rv < 0)
                return EIO;

        rchunk->riffr->pos = dest;
        return EOK;
}

/** Return chunk data size.
 *
 * @param rchunk RIFF chunk
 * @return Pure data size (excluding type+size header) in bytes
 */
uint32_t riff_rchunk_size(riff_rchunk_t *rchunk)
{
        return rchunk->cksize;
}

/** Return file offset where chunk ends
 *
 * @param rchunk RIFF chunk
 * @return File offset just after last data byte of the chunk
 */
static long riff_rchunk_get_end(riff_rchunk_t *rchunk)
{
        return rchunk->ckstart + rchunk->cksize;
}

/** Return file offset of first (non-padding) byte after end of chunk.
 *
 * @param rchunk RIFF chunk
 * @return File offset of first non-padding byte after end of chunk
 */
static long riff_rchunk_get_ndpos(riff_rchunk_t *rchunk)
{
        long ckend;

        ckend = riff_rchunk_get_end(rchunk);
        if ((ckend % 2) != 0)
                return ckend + 1;
        else
                return ckend;
}

/** Finish reading RIFF chunk.
 *
 * Seek to the first byte after end of chunk. It is allowed, though,
 * to return to the chunk later, e.g. using riff_rchunk_seek(@a rchunk, ..).
 *
 * @param rchunk Chunk structure
 * @return EOK on success, EIO on error.
 */
errno_t riff_rchunk_end(riff_rchunk_t *rchunk)
{
        long ckend;
        uint8_t byte;
        size_t nread;

        ckend = riff_rchunk_get_ndpos(rchunk);
        if (rchunk->riffr->pos < ckend &&
            ckend <= rchunk->riffr->pos + 512) {
                /* (Buffered) reading is faster than seeking */
                while (rchunk->riffr->pos < ckend) {
                        nread = fread(&byte, 1, sizeof(byte), rchunk->riffr->f);
                        if (nread != sizeof(byte))
                                return EIO;

                        rchunk->riffr->pos += sizeof(byte);
                }

        } else if (rchunk->riffr->pos != ckend) {
                /* Need to seek (backwards or too far) */
                if (fseek(rchunk->riffr->f, ckend, SEEK_SET) < 0)
                        return EIO;

                rchunk->riffr->pos = ckend;
        }

        return EOK;
}

/** Read data from RIFF chunk.
 *
 * Attempt to read @a bytes bytes from the chunk. If there is less data
 * left until the end of the chunk, less will be read. The actual number
 * of bytes read is returned in @a *nbytes (can even be 0).
 *
 * @param rchunk RIFF chunk for reading
 * @param buf Buffer to read to
 * @param bytes Number of bytes to read
 * @param nread Place to store number of bytes actually read
 *
 * @return EOK on success, ELIMIT if file position is not within @a rchunk,
 *         EIO on I/O error.
 */
errno_t riff_read(riff_rchunk_t *rchunk, void *buf, size_t bytes,
    size_t *nread)
{
        long pos;
        long ckend;
        long toread;

        pos = rchunk->riffr->pos;

        ckend = riff_rchunk_get_end(rchunk);
        if (pos < rchunk->ckstart || pos > ckend)
                return ELIMIT;

        toread = min(bytes, (size_t)(ckend - pos));
        if (toread == 0) {
                *nread = 0;
                return EOK;
        }

        *nread = fread(buf, 1, toread, rchunk->riffr->f);
        if (*nread == 0)
                return EIO;

        rchunk->riffr->pos += *nread;
        return EOK;
}

/** @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub