Welcome to libwheel’s documentation!¶
Contents:
Error Reporting & Debugging¶
The debug-print macros W_DEBUG
and W_DEBUGC
produce
messages on standard error when _DEBUG_PRINT
is defined before
including the wheel.h
header., By default their expansion are empty
statements. The following builds program.c
with the debug-print
statements turned on:
cc -D_DEBUG_PRINT -o program program.c
The W_FATAL
and W_BUG
macros can be used to produce fatal
errors which abort execution of the program.
The W_WARN
macro can be used to produce non-fatal warnings, which
can optionally be converted into fatal warnings at run-time when programs
are run with the W_FATAL_WARNIGS
environment variable defined to a
non-zero value in their environment.
The w_die()
function can be used to exit a program gracefully
after printing an error message to standard error.
Macros¶
-
W_DEBUG
(const char *format, ...)¶ Produces a debug-print on standard error with the given format. The macro automatically adds the function name, file name and line number to the message.
If
_DEBUG_PRINT
is not defined (the default), this macro expands to an empty statement, causing no overhead.See Formatted output for the available formatting options.
-
W_DEBUGC
(const char *format, ...)¶ Produces a “continuation” debug-print on standard error with the given format. The macro does not add the function name, file name and line number to the message (hence “continuation”).
If
_DEBUG_PRINT
is not defined (the default), this macro expands to an empty statement, causing no overhead.See Formatted output for the available formatting options.
-
W_FATAL
(const char *format, ...)¶ Writes a fatal error message to standard error with the given format, and aborts execution of the program. The macro automatically adds the function name, file name and line number where the fatal error is produced.
See Formatted output for the available formatting options.
-
W_BUG
(const char *message)¶ Marks the line where the macro is expanded as a bug: if control ever reaches the location, a fatal error is produced using
W_FATAL
instructing the user to report the issue. The date and time of the build are included in the error message.It is possible to supply an optional message to be printed next to the supplied text. Note that, if supplied, this should be a hint to help developers of the program.
Note that, as the message is optional, both these macro expansions produce valid code:
W_BUG(); W_BUG("This is a bug");
-
W_WARN
(format, ...)¶ Writes a warning to standard error with the given format. The macro automatically adds the function name, file name and line number where the warning is produced.
See Formatted output for the available formatting options.
If the
W_FATAL_WARNINGS
environment variable is defined and its value is non-zero, warnings are converted intoW_FATAL
errors and execution of the program will be aboerted.
Functions¶
-
void
w_die
(const char *format, ...)¶ Prints a message to standard error with a given format and exits the program with the
EXIT_FAILURE
status code.See Formatted output for the available formatting options.
Memory Utilities¶
The functions w_malloc()
, w_realloc()
and the function-like
macros w_new()
, w_new0()
, and w_free()
can be used
to allocate and release chunks of memory from the heap.
The function-like macros w_alloc()
, w_alloc0()
, and
w_resize()
can be used to allocate and resize chunks of memory
which contain arrays of elements of the same type.
It is highly encourages to use these instead of any other dynamic allocation facilities, as they do additional checks and will take care of aborting the running program with a meaningful message in case of out-of-memory conditions.
Macros¶
-
w_lmem
¶ Marks a variable as being pointer to a heap-allocated chunk of memory which must be freed by calling
w_free()
on it when the variable goes out of scope.For example:
if (show_frob ()) { // frob_message() allocates memory for the message using w_malloc() w_lmem char *frob_msg = get_frob_message (); w_print ("Frob: $s\n", frob_msg); // After this point, "frob_msg" goes out of scope, so w_free() // is called automatically on it to free the memory. }
Note that this macro uses variable attributes supported by GCC and Clang to implement this behavior. When building your program with any other compiler a compilation error will be generated if this macro is used.
-
w_lobj
¶ Marks a variable as being a pointer which holds a reference to to a heap-allocated object, and that
w_obj_unref()
will be called on it when the variable goes out of scope.Usage example:
if (do_frob ()) { // Keep a reference to a frob_t, increasing the reference counter. w_lobj frob_t *frob_obj = w_obj_ref (get_frob_object ()); handle_frob (frob_obj); // After this point, "frob_obj" goes out of scope, so w_obj_unref() // is called automatically on it to decrease the reference counter. }
Functions¶
-
void*
w_malloc
(size_t size)¶ Allocates a chunk of memory from the heap of a given size and returns a pointer to it. The returned pointer is guaranteed to be valid.
If it was not possible to allocate memory, a fatal error is printed to standard error and execution aborted.
-
void*
w_realloc
(void *address, size_t new_size)¶ Resizes a chunk of memory from the heap at address from its current size to a new_size.
Note that:
Passing
NULL
is valid, and it will allocate memory if the new_size is non-zero. This means that the following two expressions are equivalent:void *addr = w_realloc (NULL, 42); void *addr = w_malloc (42);
Requesting a new_size of zero causes memory to be deallocated. This means that the following two expressions are equivalent:
addr = w_realloc (addr, 0); w_free (addr);
-
void
w_free
(void *address)¶ Frees the chunk of heap memory at address and sets the pointer to
NULL
.
-
type*
w_new
(type)¶ Allocates a new chunk of memory from the heap suitable to hold a value of a certain type. Note that the pointer is returned casted to the appropriate type pointer, and no additional casts are needed:
int *value = w_new (int);
-
type*
w_new0
(type)¶ Allocates a zero-filled chunk of memory from the heap suitable to hold a value of a certain type. This is equivalent to using
w_new()
, clearing the memory withmemset()
, and then casting to the appropriate type:int *value = w_new0 (int); w_print ("$i\n", *value); // Prints "0"
-
type*
w_alloc
(type, size_t size)¶ Allocates a chunk of memory suitable to hold an array of a given size of values of a type. Note that the returned pointer is casted to the appropriate type, and no additional casts are needed:
int *point = w_alloc (int, 2); point[0] = 42; point[1] = 14;
-
type*
w_alloc0
(type, size_t size)¶ Allocates a zero-filled chunk of memory suitable to hold an array of a given size of values of a type. This is equivalent to using
w_alloc()
, clearing the memory withmemset()
, and then casting to the appropriate type:int *point = w_alloc0 (int, 2); w_print ("($i, $i)\n", point[0], point[1]); // Prints "(0, 0)"
-
type*
w_resize
(type* address, type, size_t new_size)¶ Resizes a chunk of memory at address which contains an array of elements of a given type to a new_size.
int *values = w_alloc (int, 10); if (need_more_values ()) values = w_resize (values, int, 20);
Objects¶
The object system allows to do object oriented programming in C99. The object model has the following features:
- Simple inheritance. All object types must “inherit” from
w_obj_t
. Of course, composition of objects is also possible. - Objects are typically allocated in the heap, but it is possible to
allocate them statically, or in the stack with the aid of the
W_OBJ_STATIC
macro. - Objects keep a reference counter, which can be manipulated using
w_obj_ref()
, andw_obj_unref()
. Objects are deallocated when their reference counter drops to zero. - It is possible to assign a “destructor function” to any object using
w_obj_dtor()
. - Minimal overhead: objects do not have a vtable by default, and dynamic method dispatching is not done unless explicitly added by the user.
- Uses only C99 constructs, and it does not require any special compiler support.
- Optionally, when using GCC or Clang, the reference count for an object
can be automatically decreased when a pointer to it goes out of scope,
by marking it with the
w_lobj
macro.
A number of features included in libwheel
make use of the object
system (for example, the Input/Output Streams), or includes support to seamlessly
integrate with the object system (for example, the List Container can update
the reference counter when objects are added to it).
Usage¶
This example shows how to define a base “shape” object type: shape_t
;
and two derived types for squares (square_t
) and rectangles
(rectangle_t
).
In order to have methods which work on any object derived from the shape
type, a “vtable” is added manually to perform dynamic dispatch using a
shared struct
which contains function pointers to the actual
implementations for each shape. Another valid approach would be to add
the function pointers directly in shape_t
to avoid the extra
indirection. This second approach would be better if the function pointers
to method implementations could change at runtime, at the cost of each
instance of a shape occupying some extra bytes of memory.
Header:
// Objects have no vtable by default, so one is defined manually.
typedef struct {
double (*calc_area) (void*);
double (*calc_perimeter) (void*);
} shape_vtable_t;
// Base object type for shapes.
W_OBJ (shape_t) {
w_obj_t parent; // Base object type.
shape_vtable_t *vtable; // Pointer to vtable.
};
// A square shape.
W_OBJ (square_t) {
shape_t parent; // Inherits both base object and vtable.
double side_length;
};
// A rectangular shape.
W_OBJ (rectangle_t) {
shape_t parent; // Inherits both base object and vtable.
double width;
double height;
};
// Functions used to create new objects.
extern shape_t* square_new (double side_length);
extern shape_t* rectangle_new (double width, double height);
// Convenience functions to avoid having to manually make the
// dynamic dispatch through the vtable manually in client code.
static inline double shape_calc_area (shape_t *shape) {
return (*shape->vtable->calc_area) (shape);
}
static inline double shape_calc_perimeter (shape_t *shape) {
return (*shape->vtable->calc_perimeter) (shape);
}
Implementation:
// Methods and vtable for squares.
static double square_calc_area (void *obj) {
double side_length = ((square_t*) obj)->side_length;
return side_length * side_length;
}
static double square_calc_perimeter (void *obj) {
return 4 * ((square_t*) obj)->side_length;
}
static const shape_vtable_t square_vtable = {
.calc_area = square_calc_area,
.calc_perimeter = square_calc_perimeter,
};
shape_t* square_new (double side_length) {
square_t *square = w_obj_new (square_t); // Make object.
square->parent.vtable = &square_vtable; // Set vtable.
square->side_length = side_length;
return (shape_t*) square;
}
// Methods and vtable for rectangles.
static double rectangle_calc_area (void *obj) {
rectangle_t *rect = (rectangle_t*) obj;
return rect->width * rect->height;
}
static double rectangle_calc_perimeter (void *obj) {
rectangle_t *rect = (rectangle_t*) obj;
return 2 * (rect->width + rect->height);
}
static const shape_vtable_t rectangle_vtable = {
.calc_area = rectangle_calc_area,
.calc_perimeter = rectangle_calc_perimeter,
};
shape_t*
rectangle_new (double width, double height) {
rectangle_t *rect = w_obj_new (rectangle_t); // Make object.
rect->parent.vtable = &rectangle_vtable; // Set vtable.
rect->width = width;
rect->height = height;
return (shape_t*) rect;
}
Using shapes:
// Uses the generic shape_* functions.
static void print_shape_infos (shape_t *shape) {
w_print ("Shape area: $F\n", shape_calc_area (shape));
w_print ("Shape perimeter: $F\n", shape_calc_perimeter (shape));
}
int main (void) {
w_lobj shape_t *s = square_new (10);
w_lobj shape_t *r = rectangle_new (10, 20);
print_shape_infos (s); // Works on any object derived from shape_t.
print_shape_infos (r); // Ditto.
return 0;
}
Types¶
-
w_obj_t
¶ Base type for objects.
All other object types must “derive” from this type for the objects system to work properly. This is achieved by having a member of this type as first member of object types — either explicitly or by “inheriting” it from another object type:
W_OBJ (my_type) { // Explicitly make the first member be an "w_obj_t" w_obj_t parent; }; W_OBJ (my_subtype) { // The first member itself has an "w_obj_t" as first member. my_type parent; };
Macros¶
-
W_OBJ_DECL
(type)¶ Makes a forward declaration of a object class of a certain type.
See also
W_OBJ_DEF
.
-
W_OBJ_DEF
(type)¶ Defines the structure for an object class of a certain type.
This macro should be used after the type has been declared using the
W_OBJ_DECL
macro.Typical usage involves declaring the type in a header, and the actual layout of it in an implementation file, to make the internals opaque to third party code:
// In "my_type.h" W_OBJ_DECL (my_type); // In "my_type.c" W_OBJ_DEF (my_type) { w_obj_t parent; int value; // ... };
-
W_OBJ
(type)¶ Declares and defines the structure for an object class of a certain type. This is equivalent to using
W_OBJ_DECL
immediately followed byW_OBJ_DEF
.For example:
W_OBJ (my_type) { w_obj_t parent; int value; // ... };
This is used instead of a combination of
W_OBJ_DECL
andW_OBJ_DEF
when a forward declaration is not needed, and it does not matter that the internals of how an object class is implemented are visible in headers:
-
W_OBJ_STATIC
(destructor)¶ Initializes a statically-allocated object, and sets destructor to be called before the object is deallocated by
w_obj_destroy()
.Similarly to
w_obj_mark_static()
, this macro allows to initialize objects for which the memory they occupy will not be deallocated.Typical usage involves initializing static global objects, or objects allocated in the stack, e.g.:
W_OBJ (my_type) { w_obj_t parent; int value; }; static my_type static_object = { .parent = W_OBJ_STATIC (NULL), .value = 42, }; void do_foo (void) { my_type stack_object = { .parent = W_OBJ_STATIC (NULL), .value = 32, }; use_object (&stack_object); }
Functions¶
-
void*
w_obj_ref
(void *object)¶ Increases the reference counter of an object.
The object itself is returned, to allow easy chaining of other function calls.
-
void*
w_obj_unref
(void *object)¶ Decreases the reference counter of an object.
Once the reference count for an object reaches zero, it is destroyed using
w_obj_destroy()
.The object itself is returned, to allow easy chaining of other function calls.
-
void
w_obj_destroy
(void *object)¶ Destroys an object.
If a destructor function was set for the object using
w_obj_dtor()
, then it will be called before the memory used by the object being freed.
-
void*
w_obj_dtor
(void *object, void (*destructor)(void*))¶ Registers a destructor function to be called when an object is destroyed using
w_obj_destroy()
.The object itself is returned, to allow easy chaining of other function calls.
-
void
w_obj_mark_static
(void *object)¶ Marks an object as being statically allocated.
When the last reference to an object marked as static is lost, its destructor will be called, but the area of memory occupied by the object will not be freed. This is the same behaviour as for objects initialized with the
W_OBJ_STATIC
macro. The typical use-case for this function to mark objects that are allocated as part of others, and the function is called during their initialization, like in the following example:W_OBJ (my_type) { w_obj_t parent; w_io_unix_t unix_io; }; void my_type_free (void *objptr) { w_obj_destroy (&self->unix_io); } my_type* my_type_new (void) { my_type *self = w_obj_new (my_type); w_io_unix_init_fd (&self->unix_io, 0); w_obj_mark_static (&self->unix_io); return w_obj_dtor (self, _my_type_free); }
-
type*
w_obj_new
(type)¶ Creates a new instance of an object of a given type.
Freshly created objects always have a reference count of
1
.
-
type*
w_obj_new_with_priv_sized
(type, size_t size)¶ Creates a new instance of an object of a given type, with additional space of size bytes to be used as instance private data.
A pointer to the private data of an object can be obtained using
w_obj_priv()
.
-
type*
w_obj_new_with_priv
(type)¶ Creates a new instance of an object of a given type, with additional space to be used as instance private data. The size of the private data will be that of a type named after the gicen type with a
_p
suffix added to it.A pointer to the private data of an object can be obtained using
w_obj_priv()
.Typical usage:
// In "my_type.h" W_OBJ (my_type) { w_obj_t parent; }; extern my_type* my_type_new (); // In "my_type.c" typedef struct { int private_value; } my_type_p; my_type* my_type_new (void) { my_type *obj = w_obj_new_with_priv (my_type); my_type_p *p = w_obj_priv (obj, my_type); p->private_value = 42; return obj; }
-
void*
w_obj_priv
(void *object, type)¶ Obtains a pointer to the private instance data area of an object of a given type.
Note that only objects created using
w_obj_new_with_priv_sized()
orw_obj_new_with_priv()
have a private data area. The results of using this function on objects which do not have a private data area is undefined.
Buffers¶
Buffers provide a variable-length area of memory in which data may be held and manipulated. Contained data is not interpreted, and length of it is tracked, so it is possible to add null bytes to a buffer.
Allocating a buffer is done in the stack using the W_BUF
macro.
to initialize it. After initialization, all buffer functions can be used,
and when the buffer is not needed anymore, its contents can be freed using
w_buf_clear()
.
Usage¶
// Initialize the buffer.
w_buf_t b = W_BUF;
// Append some string pieces.
w_buf_append_str (&b, "Too much work ");
w_buf_append_str (&b, "and no joy makes");
w_buf_append_str (&b, " Jack a dull boy");
// Buffer contents can be printed directly using the $B format.
w_print ("$B\n", &b);
// Free the memory used by the contents of the buffer.
w_buf_clear (&b);
Macros¶
-
W_BUF
¶ Initializer for buffers. It can be used to initialize buffers directly on the stack:
w_buf_t buffer = W_BUF;
Functions¶
-
void
w_buf_resize
(w_buf_t* buffer, size_t size)¶ Adjust the size of a buffer keeping contents. This is mostly useful for trimming contents, when shrinking the buffer. When a buffer grows, random data is liklely to appear at the end.
Parameters: - buffer – A w_buf_t buffer
- size – size New size of the buffer
-
void
w_buf_set_str
(w_buf_t *buffer, const char *string)¶ Set the contents of a buffer to a C string.
Parameters: - buffer – A w_buf_t buffer
- string – String to set the buffer to.
-
void
w_buf_append_mem
(w_buf_t *buffer, const void *address, size_t length)¶ Appends the contents of a chunk of memory of length bytes starting at address to a buffer.
Parameters: - buffer – A w_buf_t buffer.
- address – Pointer to the memory block of memory.
- length – Length of the memory block.
-
void
w_buf_append_buf
(w_buf_t *buffer, const w_buf_t *other)¶ Appends the contents of other buffer to another buffer.
-
char*
w_buf_str
(w_buf_t *buffer)¶ Obtains the contents of a buffer as a
NULL
-terminated C string.Warning
If the buffer contains embedded null characters, functions like
strlen()
will not report the full length of the buffer.The returned pointer is owned by the buffer, and there two ways in which the memory region can be freed:
- Clearing the buffer with
w_buf_clear()
. The returned pointer will be invalid afterwards. - Calling
w_free()
on the returned pointer. The buffer will be invalid and must not be used afterwards.
The second way is useful to assemble a string which is returned from a function, for example:
char* concat_strings (const char *s, ...) { w_buf_t buffer = W_BUF; w_buf_set_str (&buffer, s); va_list args; va_start (args, s); while ((s = va_args (args, const char*))) w_buf_append_str (&buffer, s); va_end (args); return w_buf_str (&buffer); }
- Clearing the buffer with
-
w_io_result_t
w_buf_format
(w_buf_t *buffer, const char *format, ...)¶ Appends text with a given format into a buffer, consuming additional arguments as needed by the format.
See Formatted output for the available formatting options.
-
w_io_result_t
w_buf_formatv
(w_buf_t *buffer, const char *format, va_list arguments)¶ Appends text with a given format into a buffer, consuming additional arguments as needed by the format.
See Formatted output for the available formatting options.
-
char*
w_buf_data
(w_buf_t *buffer)¶ Obtains a pointer to the internal data stored by a buffer.
Warning
The returned value may be
NULL
when the buffer is empty.
-
const char*
w_buf_const_data
(const w_buf_t *buffer)¶ Obtains a pointer to the internal data stored by a buffer, returning it as a
const
pointer. This may be used instead ofw_buf_data()
when the data is not going to be modified.Warning
The returned value may be
NULL
when the buffer is empty.
List Container¶
The implementation uses a doubly-linked list, meaning that most operations are very efficient, and the list can be traversed both forward and backward.
The functions which use numeric indexes to refer to elements in
the list can be slow, and should be avoided if possible:
w_list_at()
, w_list_insert_at()
,
and w_list_del_at()
. Negative numeric indexes can be
passed to functions, with the same meaning as in Python: -1
refers to the last element, -2
to the element before the last,
and so on.
If a list is meant to contain objects (see Objects), it is possible
to let the list reference-count the objects (using w_obj_ref()
and w_obj_unref()
) by passing true
when creating a list
with w_list_new()
. If enabled, whenever an item is added to the
list, its reference count will be increased, and it will be decreased
when the item is removed from the list.
Usage¶
w_list_t *fruits = w_list_new (false);
w_list_append (fruits, "apples");
w_list_append (fruits, "bananas");
w_list_foreach (item, fruits) // Prints "apples bananas "
w_print ("$s ", (const char*) *item);
w_list_insert_after (fruits, w_list_first (fruits), "pears");
// fruits = {"apples", "pears", "bananas"}
w_list_del_head (fruits);
// fruits = {"pears", "bananas"}
w_list_foreach_reverse (item, fruits) // Prints "bananas pears "
w_print ("$s ", (const char*) *item);
w_obj_unref (fruits); // Decrease the reference counter, frees the list.
Macros¶
Functions¶
-
w_list_t*
w_list_new
(bool reference_counted)¶ Creates a new list, in which elements are optionally reference_counted.
-
void
w_list_push_head
(w_list_t *list, void *element)¶ Inserts an element at the beginning of a list.
-
void*
w_list_pop_head
(w_list_t *list)¶ emoves the element at the beginning of a list and returns it.
Note that this will not decrease the reference counter when reference counting is enabled: it is assumed that the caller will use the returned item.
-
void*
w_list_pop_tail
(w_list_t *list)¶ Removes the element at the end of a list and returns it.
Note that this will not decrease the reference counter when reference counting is enabled: it is assumed that the caller will use the returned item.
-
void*
w_list_at
(const w_list_t *list, long index)¶ Obtains the value stored in a list at a given index. Negative indexes count from the end of the list.
-
w_iterator_t
w_list_first
(const w_list_t *list)¶ Obtains an iterator pointing to the first element of a list.
-
w_iterator_t
w_list_last
(const w_list_t *list)¶ Obtains an iterator pointing to the last element of a list
-
w_iterator_t
w_list_next
(const w_list_t *list, w_iterator_t iterator)¶ Makes an iterator to an element of a list point to the next element in the list, and returns the updated iterator.
-
w_iterator_t
w_list_prev
(const w_list_t *list, w_iterator_t iterator)¶ Makes an iterator to an element of a list point to the previous element in the list, and returns the updated iterator.
-
void
w_list_insert_before
(w_list_t *list, w_iterator_t position, void *element)¶ Inserts an element in a list before a particular position.
-
void
w_list_insert_after
(w_list_t *list, w_iterator_t position, void *element)¶ Inserts an element in a list after a particular position.
-
void
w_list_insert_at
(w_list_t *list, long index, void *element)¶ Inserts an element in a list at a given index..
Note that the operation is optimized for some particular indexes like
0
(first position) and-1
(last position), bu in general this function runs in O(n) time depending on the size of the list.
-
void
w_list_del
(w_list_t *list, w_iterator_t position)¶ Deletes the element at a given position in a list.
-
void
w_list_del_head
(w_list_t *list)¶ Deletes the element at the beginning of a list.
Contrary to
w_list_pop_head()
, the element is not returned, and the reference counter is decreased (if reference counting is enabled).
-
void
w_list_del_tail
(w_list_t *list)¶ Deletes the element at the end of a list.
Contrary to
w_list_pop_tail()
, the element is not returned, and the reference counter is decreased (if reference counting is enabled).
-
void
w_list_insert
(w_list_t *list, w_iterator_t position, void *element)¶ Alias for
w_list_insert_before()
.
-
void
w_list_append
(w_list_t *list, void *element)¶ Alias for
w_list_push_tail()
.
-
void
w_list_pop
(w_list_t *list, void *element)¶ Alias for
w_list_pop_tail()
.
Input/Output Streams¶
The following kinds of streams are provided:
- Input/Output on Buffers
- Input/Output on memory
- Input/Output on FILE* streams
- Input/Output on Unix file descriptors
- Input/Output on Sockets
Formatted output¶
A number of functions support specifying a format string, and will accept a variable amount of additional function arguments, depending on the format specifiers present in the format string. All those functions use the same formatting mechanism, as described here.
Format specifiers are sequences of characters started with a dollar
symbol ($
), followed by at a character, which determines the type
and amount of the additional function arguments being consumed.
The recognized format specifiers are:
Specifier | Type(s) | Output format. |
---|---|---|
$c |
int | Character. |
$l |
long int | Decimal number. |
$L |
unsigned long int | Decimal number. |
$i |
int | Decimal number. |
$I |
unsigned int | Decimal number. |
$X |
unsigned long int | Hexadecimal number. |
$O |
unsigned long int | Octal number. |
$p |
void* | Pointer, as hexadecimal number. |
$f |
float | Floating point number. |
$F |
double | Floating point number. |
$s |
const char* | A \0 -terminated string. |
$B |
w_buf_t* | Arbitrary data (usually a string). |
$S |
size_t, const char* | String of a particular length. |
$e |
Last value of errno , as an integer. |
|
$E |
Last value of errno , as a string. |
|
$R |
w_io_result_t | String representing the return value of an input/output operation (see Output for w_io_result_t below). |
Output for w_io_result_t
¶
The $R
format specifier will consume a w_io_result_t
value,
and format it as a string, in one of the following ways:
IO<EOF>
- End-of-file marker. This is printed when
w_io_eof()
would returntrue
for the value. IO<string>
- This format is used for errors, the string describes the error.
Most of the time the error strings correspond to the values obtained
by using
strerror (w_io_result_error (value))
on the value. IO<number>
- This format is used for successful operations, the number is the
amount of bytes that were handled during the input/output operation,
and it can be zero, e.g. for the result of
w_io_close()
.
Reusing¶
The main entry point of the formatting mechanism is the
w_io_format()
function, which works for any kind of output stream.
To allow for easier integration with other kinds of output, an alternate
version of the function, w_io_formatv()
, which accepts a
va_list
, is provided as well.
By providing a custom output stream implementation, it is possible to reuse
the formatting mechanism for your own purposes. The w_buf_format()
function, which writes formatted data to a buffer, is implemented using this
technique.
The following example implements a function similar to asprintf()
, which
allocates memory as needed to fit the formatted output, using
w_io_formatv()
in combination with a w_io_buf_t
stream:
char*
str_format (const char *format, ...)
{
// Using NULL uses a buffer internal to the w_io_buf_t.
w_io_buf_t buffer_io;
w_io_buf_init (&buffer_io, NULL, false);
// Writing to a buffer always succeeds, the result can be ignored.
va_list args;
va_start (args, format);
W_IO_NORESULT (w_io_formatv ((w_io_t*) &buffer_io, format, args));
va_end (args);
// The data area of the buffer is heap allocated, so it is safe to
// return a pointer to it even when the w_io_buf_t is in the stack.
return w_io_buf_str (&buffer_io);
}
Types¶
-
w_io_result_t
¶ Represents the result of an input/output operation, which is one of:
- An error, which signals the failure of the operation. The
w_io_failed()
can be used to check whether an input/output operation failed. If case of failure, the error code can be obtained usingw_io_result_error()
; otherwise the operation succeeded and it can have one of the other values. - An indication that the end-of-file marker has been reached — typically
used when reading data from a stream. The
w_io_eof()
function can be used to check for the end-of-file marker. - A successful operation, indicating the amount of data involved. This
is the case when an operation neither failed, neither it is the
end-of-file marker. The amount of bytes handled can be retrieved using
w_io_result_bytes()
.
It is possible to obtain a textual representation of values of this type by using the
$R
format specifier with any of the functions that use the Formatted output system.- An error, which signals the failure of the operation. The
-
w_io_t
¶ Represents an input/output stream.
Macros¶
-
W_IO_RESULT
(bytes)¶ Makes a
w_io_result_t
value which indicates a successful operation which handled the given amount of bytes.
-
W_IO_RESULT_ERROR
(error)¶ Makes a
w_io_result_t
value which indicates a failure due to given error.
-
W_IO_RESULT_EOF
¶ Makes a
w_io_result_t
value which indicates a successful operation that reached the end-of-file marker.
-
W_IO_RESULT_SUCCESS
¶ Makes a
w_io_result_t
value which indicates a successful operation.
-
W_IO_CHAIN
(result, expression)¶ This macro expands to code that evaluates an expression, which must return a
w_io_result_t
value. If the operation failed, it will cause the current function to return the error, otherwise the amount of bytes returned byw_io_result_bytes()
are added to the amount in the variable of typew_io_result_t()
passed as the result parameter.Typical usage involves using the macro is inside a function that performs a number of reads (or writes), and the overall status of a “chain” of input/output operations is to be reported back as a result.
The expanded macro is roughly equivalent to:
w_io_result_t expr_result = expression; if (w_io_failed (expr_result)) return expr_result; result.bytes += w_io_result_bytes (expr_result);
As an example, consider a data stream which contains “messages” of the form
SIZE:DATA
, with the SIZE of the message encoded as a plain text integer, followed by a colon, and SIZE bytes of arbitrary data. The following function reads such messages, one at a time, returning the result of reading from the input as aw_io_result_t
value —which can be checked for end-of-file or errors—, and returning the DATA in a buffer and its expected size:w_io_result_t read_message (w_io_t *input, w_buf_t *message, unsigned long *size) { w_io_result_t bytes = W_IO_RESULT (0); // Read the length of the record. If reading fails, the macro // causes the function to return early with an error result. // The amount of bytes read is added to "bytes". W_IO_CHAIN (bytes, w_io_fscan_ulong (input, size)); // Read the separator. Again: the macro causes an early error // return on failure, or incrementing "bytes" on success. char separator = '\0'; W_IO_CHAIN (bytes, w_io_read (input, &separator, 1)); if (separator != ':') return W_IO_RESULT_ERROR (EBADMSG); // Read the message contents. Again: the macro causes an early // error on failure, or incrementing "bytes" on success. w_buf_resize (message, record_size); W_IO_CHAIN (bytes, w_io_read (input, w_buf_data (message), *size); return bytes; // Returns the total amount of bytes read. }
-
W_IO_CHECK
(expression)¶ This macro expands to code that evaluates an expression, which must return a
w_io_result_t
value. If the operation failed, it will cause the current function to return the error. It is roughly equivalent to:w_io_result_t expr_result = expression; if (w_io_failed (expr_result)) return expr_result;
-
W_IO_CHECK_RETURN
(expression, value)¶ Similar to
W_IO_CHECK
, but instead of returning aw_io_result_t
if the expression fails to perform input/output, the given value is returned instead. It is roughly equivalent to:if (w_io_failed (expression)) return value;
-
W_IO_NORESULT
(expression)¶ This macro expands to code that evaluates an expression, which must return a
w_io_result_t
value, and ignoring that result value. This is typically used when a an input/output operation is known to never fail, and the result status will not be checked, or when the operation might fail, but it does not matter whether it did.
Functions¶
-
w_io_result_t
w_io_close
(w_io_t *stream)¶ Closes an input/output stream.
-
w_io_result_t
w_io_read
(w_io_t *stream, void *buffer, size_t count)¶ Reads up to count bytes from the an input stream, placing the data in in memory starting at buffer.
Passing a count of zero always succeeds and has no side effects.
If reading succeeds, the amount of bytes read may be smaller than the requested count. The reason may be that the end-of-file marker has been reached (and it will be notified at the next attempt of reading data), or because no more data is available for reading at the moment.
-
w_io_result_t
w_io_write
(w_io_t *stream, const void *buffer, size_t count)¶ Writes up to count bytes from the data in memory starting at buffer to an output stream.
Passing a count of zero always succeeds and has no side effects.
-
int
w_io_getchar
(w_io_t *stream)¶ Reads the next character from a input stream.
If the enf-of-file marker is reached,
W_IO_EOF
is returned. On errors, negative values are returned.
-
w_io_result_t
w_io_putchar
(w_io_t *stream, int character)¶ Writes a character to an output stream.
-
void
w_io_putback
(w_io_t *stream, int character)¶ Pushes a character back into an input stream, making it available during the next read operation.
Warning
Pushing more than one character is not supported, and only the last pushed one will be saved.
-
w_io_result_t
w_io_flush
(w_io_stream *stream)¶ For an output stream, forces writing buffered data to the stream.
For in input stream, discards data that may have been fetched from the stream but still not consumed by the application.
-
int
w_io_get_fd
(w_io_t *stream)¶ Obtains the underlying file descriptor used by a stream.
Warning
Not all types of input/output streams have an associated file descriptor, and a negative value will be returned for those.
-
w_io_result_t
w_io_format
(w_io_t *stream, const char *format, ...)¶ Writes data with a given format to an output stream. The amount of consumed arguments depends on the format string.
See Formatted output for more information.
-
w_io_result_t
w_io_formatv
(w_io_t *stream, const char *format, va_list arguments)¶ Writes data with a given format to an output stream, consuming the needed additional arguments from the supplied
va_list
. The amount of consumed arguments depends on the format string.See Formatted output for more information.
-
w_io_result_t
w_print
(format, ...)¶ Writes data in the given format to the standard output stream
w_stdout
. The amount of consumed arguments depends on the format string.See Formatted output for more information.
-
w_io_result_t
w_printerr
(format, ...)¶ Writes data in the given format to the standard error stream
w_stderr
. The amount of consumed arguments depends on the format string.See Formatted output for more information.
-
w_io_result_t
w_io_read_until
(w_io_t *stream, w_buf_t *buffer, w_buf_t *overflow, int character, unsigned read_bytes)¶ Reads data from a stream until a certain character is read. Data is read in chunks of read_bytes size, and placed in a buffer, with the excess characters placed in an overflow buffer, which can be passed back to continue scanning for the stop character in subsequent calls.
Passing zero for the read_bytes size will make the function use a default chunk size — usually the size of a memory page.
This function is intended to read records of data of variable size which are separated using a certain character as a delimiter. For example, usually fortune data files have items separated by
%
characters. The following function would read such a file:void read_fortunes (w_io_t *input) { w_buf_t fortune = W_BUF; w_buf_t overflow = W_BUF; for (;;) { w_io_result_t r = w_io_read_line (input, &fortune, &overflow, 0); if (w_io_failed (r)) { w_printerr ("Error reading fortunes: $R\n" r); break; } if (w_io_eof (r)) break; handle_fortune (&fortune); } w_buf_clear (&overflow); w_buf_clear (&fortune); }
-
bool
w_io_failed
(w_io_result_t result)¶ Checks whether the result of an input/output operation was a failure.
If the result happens to be a failure,
w_io_result_error()
can be used to retrieve the error code.
-
bool
w_io_eof
(w_io_result_t result)¶ Checks whether the result of an input/output operation was the end-of-file marker.
-
int
w_io_result_error
(w_io_result_t result)¶ Obtains the code of the error that caused an input/output operation to return a failure result.
This function only returns meaningful values when result indicates a failed operation. This condition can be checked using
w_io_failed()
.
-
size_t
w_io_result_bytes
(w_io_result_t result)¶ Obtains the amount of bytes which were involved as the result of an input/output operation.
When the result signals a failed operation, or the end-of-file marker, the returned value is always zero.
-
w_io_result_t
w_io_read_line
(w_io_t *stream, w_buf_t *line, w_buf_t *overflow, unsigned read_bytes)¶ Reads a line of text from an input stream.
This is a convenience function that calls
w_io_read_until()
passing'\n'
as delimiter character.
Input/Output on Buffers¶
Provides support for using the stream functions to
read and write to and from w_buf_t
buffers.
Functions¶
-
void
w_io_buf_init
(w_io_buf_t *stream, w_buf_t *buffer, bool append)¶ Initialize a stream object (possibly allocated in the stack) to be used with a buffer.
Passing a
NULL
buffer will create a new buffer owned by the stream object, which can be retrieved usingw_io_buf_get_buffer()
. The memory used by this buffer will be freed automatically when the stream object is freed. On the contrary, when a valid buffer is supplied, the caller is responsible for callingw_buf_clear()
on it.Optionally, the stream position can be setup to append data to the contents already present in the given buffer, insted of overwriting them.
-
w_io_t*
w_io_buf_open
(w_buf_t *buffer)¶ Creates a stream object to be used with a buffer.
Passing a
NULL
buffer will create a new buffer owned by the stream object, which can be retrieved usingw_io_buf_get_buffer()
. The memory used by this buffer will be freed automatically when the stream object is freed. On the contrary, when a valid buffer is supplied, the caller is responsible for callingw_buf_clear()
on it.
-
w_buf_t*
w_io_buf_get_buffer
(w_io_buf_t *stream)¶ Obtain a pointer to the buffer being used by a stream.
-
char*
w_io_buf_str
(w_io_buf_t *stream)¶ Obtain a string representation of the contents of the buffer being used by a stream.
Input/Output on memory¶
Provides support for using the stream functions to read and write to and from regions of memory of fixed sizes.
Functions¶
-
void
w_io_mem_init
(w_io_mem_t *stream, uint8_t *address, size_t size)¶ Initializes a stream object (possibly located in the stack) to be used with a region of memory of a given size located at address.
-
w_io_t*
w_io_mem_open
(uint8_t *address, size_t size)¶ Creates a stream object to be used with a region of memory of a given size located at address.
-
uint8_t*
w_io_mem_data
(w_io_mem_t *stream)¶ Obtains the base address to the memory region on which a stream operates.
-
size_t
w_io_mem_size
(w_io_mem_t *stream)¶ Obtains the size of the memory region on which a stream operates.
Input/Output on FILE*
streams¶
Provides support for using the stream functions to
read and write to and from FILE*
streams as provided by the C standard
library.
Functions¶
-
void
w_io_stdio_init
(w_io_stdio_t *stream, FILE *stdio_stream)¶ Initializes a stream object (possibly allocated in the stack) to be used with a given stdio_stream.
Input/Output on Unix file descriptors¶
Once a Unix stream object has been initialized, they can be operated used the common stream functions.
Functions¶
-
w_io_t*
w_io_unix_open
(const char *path, int mode, unsigned permissions)¶ Creates a stream object to be used with an Unix file descriptor by opening the file at path with the given mode and permissions.
This is a convenience function that calls
open()
and then usesw_io_unix_open_fd()
.If opening the file fails,
NULL
is returned.
-
bool
w_io_unix_init
(w_io_unix_t *stream, const char *path, int mode, unsigned permissions)¶ Initializes a stream object (possibly allocated in the stack) to be used with an Unix file descriptor by opening the file at path with the given mode and permissions.
This is a convenience function that calls
open()
and then usesw_io_unix_init_fd()
.The return value indicates whether the file was opened successfully.
-
void
w_io_unix_init_fd
(w_io_unix_t *stream, int fd)¶ Initializes a stream object (possibly allocated in the stack) to be used with an Unix file descriptor.
Input/Output on Sockets¶
Provides support for using the stream functions to read and write to and from sockets.
The following kinds of sockets are supported:
- TCP sockets.
- Unix sockets.
Usage¶
Client¶
To connect a socket to a remote server (and use it as a client), the usual procedure is as follows:
- Create the socket with
w_io_socket_open()
. - Connect to the remote endpoint using
w_io_socket_connect()
. - Exchange data using
w_io_write()
andw_io_read()
, or any other of the stream functions. - (Optional) Once no more data needs to be written, a half-close can be
performed using
w_io_socket_send_eof()
. It will still be possible to read data from the stream. - Close the socket, using
w_io_close()
or callingw_obj_unref()
on it and letting it be closed and destroyed when no more references to the socket are held.
The following code will make an HTTP request and read the response back
into a w_buf_t
:
w_buf_t
http_get (const char *ip_address, unsigned port, const char *resource)
{
w_lobj w_io_t *stream = w_io_socket_open (W_IO_SOCKET_TCP4,
ip_address,
port);
if (!stream || !w_io_socket_connect ((w_io_socket_t*) stream))
w_die ("Cannot create socket and connect to $s\n", ip_address);
W_IO_NORESULT (w_io_format (stream, "GET $s HTTP/1.0\r\n", resource));
w_io_socket_send_eof ((w_io_socket_t*) stream);
w_buf_t response = W_BUF;
char buf[512];
for (;;) {
w_io_result_t r = w_io_read (stream, buf, w_lengthof (buf));
if (w_io_failed (r))
w_die ("Error reading from $s: $R\n", ip_address, r);
if (w_io_result_bytes (r) > 0)
w_buf_append_mem (&response, buf, w_io_result_bytes (r));
else if (w_io_eof (r))
break;
}
return response;
}
Server¶
Using a socket to server requests is a bit more convoluted, but still much easier than using the sockets API directly. The overall procedure is:
Define a request handler function which conforms to the following signature:
bool (*request_handler) (w_io_socket_t *socket)
Create the socket with
w_io_socket_open()
.Start serving requests with
w_io_socket_serve()
. The function will bind to the address specified when creating the socket and start serving requests. For each request, the request handler function will be called with a socket that can be used to handle the request.
The following code implements a simple TCP echo server:
static bool
handle_echo_request (w_io_socket_t *socket)
{
char buf[512];
for (;;) {
w_io_result_t r = w_io_read ((w_io_t*) socket, buf, w_lengthof (buf));
if (w_io_failed (r))
w_die ("Error reading from client: $R\n", r);
if (w_io_eof (r))
break;
r = w_io_write ((w_io_t*) socket, buf, w_io_result_bytes (r));
if (w_io_failed (r))
w_die ("Error writing to client: $R\n", r);
}
w_io_socket_send_eof (socket);
return true; // Keep accepting more requests.
}
int main (void)
{
w_lobj w_io_t *socket = w_io_socket_open (W_IO_SOCKET_TCP4,
"0.0.0.0", // Address.
4242); // Port.
if (!socket)
w_die ("Cannot create socket: $E\n");
if (!w_io_socket_serve ((w_io_socket_t*) socket,
W_IO_SOCKET_FORK, // Handle each request in a child process.
handle_echo_request))
w_dir ("Cannot accept connections: $E\n");
return 0;
}
Functions¶
-
bool
w_io_socket_init
(w_io_socket_t *stream, w_io_socket_kind_t kind, ...)¶ Initializes a socket stream (possibly allocated in the stack).
For a description of the additional function arguments, please check the documentation for
w_io_socket_open()
.
-
w_io_t*
w_io_socket_open
(w_io_socket_kind_t kind, ...)¶ Create a new socket of a given kind.
Sockets are created in a state in which they can be used both for client and server sockets:
w_io_socket_connect()
will put the socket in client mode and connect it to the specified address.w_io_socket_serve()
will put the socket in server mode, and start listening for connections at the specified address.
The parameters that need to be passed to this function vary depending on the kind of the socket being created.
- Unix sockets:
- Pass
W_IO_SOCKET_UNIX
as kind, and the path in the file system where the socket is to be created (or connected to). - TCP sockets:
- Pass
W_IO_SOCKET_TCP4
as kind, plus the IP address (as a string) and the port to use (or to connect to).
-
bool
w_io_socket_connect
(w_io_socket_t *socket)¶ Connect a socket to a server.
This makes a connection to the host specified when creating the socket with
w_io_socket_open()
, and puts it in client mode. Once the socket is successfully connected, read and write operations can be performed in the socket.The return value indicates whether the connection was successful.
-
bool
w_io_socket_serve
(w_io_socket_t *socket, w_io_socket_serve_mode_t mode, bool (*handler)(w_io_socket_t*))¶ Serves requests using a socket. This function will start a loop accepting connections, and for each connection an open socket will be passed to the given handler function. The mode in which each request is served can be specified:
W_IO_SOCKET_SINGLE
: Each request is served by calling directly and waiting for it to finish. This makes impossible to serve more than one request at a time.W_IO_SOCKET_THREAD
: Each request is served in a new thread. The handler is invoked in that thread.W_IO_SOCKET_FORK
: A new process is forked for each request. The handler is invoked in the child process.
-
bool
w_io_socket_send_eof
(w_io_socket_t *socket)¶ Half-close a socket on the write direction. This closes the socket, but only in writing one direction, so other endpoint will think that the end of the stream was reached (thus the operation is conceptually equivalent to sending and “end of file marker”). Read operations can still be performed in a socket which was half-closed using this function.
Note that for completely closing the socket,
w_io_close()
should be used instead.The return value indicates whether the half-close was successful.
-
const char*
w_io_socket_unix_path
(w_io_socket_t *socket)¶ Obtain the path in the filesystem for an Unix socket.
Note that the result is undefined if the socket is of any other kind than an Unix socket.
Tasks¶
The tasks system provides “green threads” (also known as lightweight cooperative threads, or coroutines).
Additionally, the following utilities are provided to be used along with the tasks system:
- Functions to suspend a coroutine and wait for I/O to be completed:
w_task_yield_io_read()
,w_task_yield_io_write()
. - The
w_io_task_open()
andw_io_task_init()
functions can be used to create aw_io_task_t
wrapper to ease using asynchronous I/O with tasks.
Types¶
-
w_task_t
¶ Type of a task.
Tasks are created by
w_task_prepare()
, and the resources used by a task (including the stack space used by the task and thew_task_t
value itself) will be automatically freed when the tasks is exited. Never deallocate a task manually, or use it after it has been exited.
-
w_task_func_t
¶ Type of task functions.
-
w_io_task_t
¶ Stream wrapper for asynchronous input/output.
This is a subclass of :type:w_io_t; all the stream functions can be used on objects of this type. Reading and writing data uses
w_task_yield_io_read()
andw_task_yield_io_write()
, so instead of blocking until completion the current task will be suspended automatically.
Functions¶
-
w_task_t*
w_task_prepare
(w_task_func_t function, void *data, size_t stack_size)¶ Creates a task with a given stack_size and prepares it for running a function, passing a data pointer to the function. The task will be in paused state upon creation.
To get tasks running, the scheduler must be running, see
w_task_run_scheduler()
.The stack_size is always rounded up to the size of a memory page. It is possible to pass zero to get the smallest possible stack size (usually 4 kB).
-
w_task_t*
w_task_current
()¶ Obtains the task currently running.
Warning
This function must be called from inside a task, once the task scheduler has been started. Otherwise, calling this function is an error and the execution of the program will be aborted.
-
void
w_task_set_is_system
(w_task_t *task, bool is_system)¶ Set whether a task is a system task. System tasks are those which are always running.
System tasks do not prevent
w_task_run_scheduler()
from returning.
-
bool
w_task_get_is_system
(w_task_t *task)¶ Checks whether a task is a system task.
See also
w_task_set_is_system()
.
-
void
w_task_set_name
(w_task_t *task, const char *name)¶ Sets the name of a task. The name of the tast is copied as-is, and it is not interpreted in any way. The ability of naming tasks is mainly provided as an aid for debugging client code.
It is possible to pass
NULL
as the name, which will clear any custom name previously set.
-
const char*
w_task_get_name
(w_task_t *task)¶ Obtains the name of a task.
If a name has not been set using
w_task_set_name()
, an autogenerated one of the formTask<ID>
will be returned.
-
void
w_task_run_scheduler
()¶ Runs the task scheduler.
The scheduler will choose tasks in a round-robin fashion, and let each task run until it gives up the CPU explicitly using
w_task_yield()
or implicitly when waiting for input/output on a stream be means ofw_task_yield_io_read()
andw_task_yield_io_write()
.The scheduler will keep scheduling tasks until all non-system tasks have been exited.
This function must be called in the main function of a program. Typically:
extern void process_argument (void*); int main (int argc, char **argv) { while (argc--) w_task_prepare (process_argument, *argv++, 0); w_task_run_scheduler (); return 0; }
-
void
w_task_yield
()¶ Make the current task give up the CPU, giving control back to the task scheduler, which will give other other tasks the chance to run.
-
void
w_task_exit
()¶ Exits the current task. This can be used to exit from a task at any point, without needing to return from the task function.
-
w_io_result_t
w_task_yield_io_read
(w_io_t *stream, void *buffer, size_t count)¶ Reads count bytes into the memory block at buffer from an input stream, suspending the current task as needed.
If the stream has been set as non-blocking and reading from it results in an
EAGAIN
orEWOULDBLOCK
error, the current task will give up the CPU and wait until the data is available for reading as many times as needed, until count bytes are read, the end-of-file marker is reached, or an error is found.
-
w_io_result_t
w_task_yield_io_write
(w_io_t *stream, const void *buffer, size_t count)¶ Writes count bytes from the memory block at buffer to an output stream, suspending the current task as needed.
If the stream has been set as non-blocking and writing to it results in an
EAGAIN
orEWOULDBLOCK
error, the current task will give up the CPU and wait until the stream accepts writing data as many times as needed, until count bytes are written, or an error is found.
-
bool
w_io_task_init
(w_io_task_t *wrapper, w_io_t *stream)¶ Initializes a stream wrapper object (possibly allocated in the stack) which wraps a stream. The wrapper behaves like the wrapped stream, suspending the current task when needed to ensure that I/O is performed asynchronously.
The return value indicates whether the stream can be wrapped. Most of the streams for which
w_io_get_fd()
returns a valid file descriptor can be wrapped.
-
w_io_t*
w_io_task_open
(w_io_t *stream)¶ Wraps a stream and returns an object that behaves like the wrapped stream, suspending the current task when needed to ensure that I/O is performed asynchronously.
Returns
NULL
when the stream cannot be wrapped. Most of the streams for whichw_io_get_fd()
returns a valid file descriptor can be wrapped.
-
void
w_task_system
()¶ Mark the current task as a system task.
See also
w_task_set_system()
.
-
const char*
w_task_name
()¶ Obtain the name of the current task.
See also
w_task_get_name()
.