Specification of DUMBFILE_SYSTEM
================================

DUMB is designed filesystem-agnostic, even though the C standard library
already defines an abstraction over files on a disk. This is useful because
Allegro 4 and 5 define their own abstractions.

To register your own filesystem abstraction with DUMB, you must create an
instance of struct `DUMBFILE_SYSTEM`, fill in your own function pointers
according to the specification below, and call `register_dumbfile_system` on
your instance.

The header `dumb.h` defines `DUMBFILE_SYSTEM` as a struct of function pointers:

```
typedef struct DUMBFILE_SYSTEM
{
    void *(*open)(const char *filename);
    int (*skip)(void *f, dumb_off_t n);
    int (*getc)(void *f);
    dumb_ssize_t (*getnc)(char *ptr, size_t n, void *f);
    void (*close)(void *f);
    int (*seek)(void *f, dumb_off_t n);
    dumb_off_t (*get_size)(void *f);
}
DUMBFILE_SYSTEM;
```

Here, `dumb_off_t` is a signed integer at least 64 bits wide, it is intended
to measure file offsets. The return type `dumb_ssize_t` is a signed integer
exactly as wide as `size_t`, it is intended to store either a `size_t` or a
negative error code. Both `dumb_*_t` are defined in `dumb.h`.

The function pointers `skip` and `getnc` are optional, i.e., you may set
some of these to `NULL` in your struct instance. DUMB will then try to
mimick the missing functions' behavior by calling your `getc` several times.
If DUMB is built with debugging flags, it will assert that all other
functions are not `NULL`. In release mode, DUMB will silently fail.

Your non-`NULL` function pointers must conform to the following specification.



open
----

```
void *(*open)(const char *filename);
```

Open a file for reading.

Arguments:

* `const char *filename`: A normal filename as understood by the operating
    system. Will be opened for reading.

Returns as `void *`:

* the address of a file handle on successfully opening the file.
    DUMB will pass this file handle as argument to other functions of
    the `DUMBFILE_SYSTEM`.

* `NULL` on error during opening the file.

Each file has a *position* internally managed by DUMB. A newly opened file
has a position of 0. Other functions from the `DUMBFILE_SYSTEM` can move
this position around.

DUMB allocates memory for the successfully opened file, and will store opaque
information in that memory, e.g., the DUMB-internal file position. This memory
be freed when DUMB calls `close` on the file's handle. The memory is separate
from your own filesystem implementation: You are responsible for supplying the
data, and DUMB is responsible for storing anything about interpreting that
data.



skip
----

```
int (*skip)(void *f, dumb_off_t n);
```

Advance the position in the file.

Arguments:

* `void *f`: A file handle that `open` returned. Guaranteed non-`NULL`.

* `dumb_off_t n`: Number of bytes to advance in the file. DUMB will only
call this with `n >= 0`. For `n < 0`, the behavior of `skip` is undefined.

Returns as `int`:

* `0` on successfully skipping ahead by `n` bytes.

* `-1` on error.

It is legal to set `skip = NULL` in a `DUMBFILE_SYSTEM`. DUMB will then call
`getc` a total of `n` times to skip ahead in a file. For speed, it is
advisable to supply a proper `skip` implementation.



getc
----

```
int (*getc)(void *f);
```

Read a byte from the file.

Arguments:

* `void *f`: A file handle that `open` returned. Guaranteed non-`NULL`.

Returns as `int`:

* the value of the byte read, on successfully reading one byte.

* `-1` on error.

After a succesful read, DUMB will treat the file as advanced by one byte.



getnc
-----

```
dumb_ssize_t (*getnc)(char *ptr, size_t n, void *f);
```

Read up to the given number of bytes from the file into a given buffer.

* `char *ptr`: The start of a buffer provided by DUMB.

* `size_t n`: The length of the number of bytes to be read.

* `void *f`: A file handle that `open` returned. Guaranteed non-`NULL`.

Returns as `dumb_ssize_t`:

* the number of bytes successfully read, if it was possible to read at least
one byte.

* `-1` on error.

This function shall bytes from the file `f` and store them in sequence in the
buffer beginning at `ptr`. It shall read fewer than `n` bytes if end of file
is encountered before `n` bytes could have been read, otherwise it should read
`n` bytes.

It is legal to set `skip = NULL` in a `DUMBFILE_SYSTEM`. DUMB will then call
`getc` a total of `n` times and store the results in its buffer.



close
-----

```
void (*close)(void *f);
```

Closes a file that has been opened before with `open`.

Arguments:

* `void *f`: A file handle that `open` returned. Guaranteed non-`NULL`.

DUMB will deallocate the memory that it used to interpret the file. You are
free to treat your resource however you would like: You may deallocate it, or
keep it around for other things. For example, Allegro 5's implementation
of `close` takes a void pointer and does nothing with it at all.



seek
----

```
int (*seek)(void *f, dumb_off_t n);
```

Jump to an arbitrary position in the file.

Arguments:

* `void *f`: A file handle that `open` returned. Guaranteed non-`NULL`.

* `dumb_off_t n`: The position in the file, relative to the beginning.
    There is no guarantee whether `n >= 0`.

Returns as `int`:

* `0` on successfully seeking in the file.

* `-1` on error.

DUMB will modify its internal position of the file accordingly.

A value of `n < 0` shall set the file into an erroneous state from which no
bytes can be read.



get_size
--------

```
dumb_off_t (*get_size)(void *f);
```

Get the length in bytes, i.e., the position after the final byte, of a file.

Arguments:

* `void *f`: A file handle that `open` returned. Guaranteed non-`NULL`.

Returns as `dumb_off_t`:

* the length of the file in bytes.
