Camelot Documentation
Search... Ctrl+K

I/O Utilities

The I/O subsystem provides a platform-agnostic abstraction layer that wraps POSIX and Windows APIs into Camelot’s Result tri-state error model. All operations route through the Allocator VTable to maintain memory lifetime ownership.

File Operations

Direct interaction with POSIX or Windows APIs creates platform-specific memory leaks, file descriptor leaks and inconsistent error codes. Camelot’s I/O module wraps these behind a clean, Result-returning interface:

Result IO_read(Allocator* alloc, String path);
Result IO_write(Allocator* alloc, String path, Slice data);

Both functions:

  • Accept an Allocator* for any internal memory needs (e.g., read buffers)
  • Return a Result with OK (success payload), NIL (no data) or ERR (system failure with domain-prefixed error code)
  • Use the Explicit Deferral pattern (goto defer) to ensure all resources are freed on every exit path

Error Mapping

All OS-level errors are translated into Camelot’s domain-prefixed error codes:

#define ERR_FILE_ERROR    (DOMAIN_CAMELOT | 0x0002)

This ensures client code never needs to inspect raw errno values or POSIX/Windows-specific error constants.

Safe String Interoperability

When Camelot String values (non-owning slices) cross into libc boundaries (e.g., file paths for fopen), developers typically resort to strcpy or sprintf and reintroduce overflow risk. Camelot solves this with allocator-aware string construction:

OwnedString

The OwnedString type pairs allocated string data with its originating Allocator*, conforming to the Explicit Deinit pattern:

typedef struct {
    Allocator* alloc;    // Originating allocator for teardown
    String view;         // Non-owning slice view of the data
} OwnedString;

Allocator-Aware Formatting

[[nodiscard]] Result STRING_format(Allocator* alloc, const char* fmt, ...);
[[nodiscard]] Result STRING_formatVariadic(Allocator* alloc, const char* fmt, va_list args);

These functions replace raw asprintf() and vasprintf(), which bypass the Allocator VTable entirely and create double-free hazards. The returned Result contains an OwnedString* on success.

Teardown

void OWNEDSTRING_deinit(OwnedString* str);

This returns memory to the originating allocator, preventing double-frees and lifetime mismatches.

Interop Rules

AllowedProhibited
snprintf() for libc boundary crossingsstrcpy, strcat, strncpy, strncat (poisoned)
memccpy() for bounded copiesRaw asprintf() / vasprintf()
STRING_format() for allocator-aware formattingAny format function bypassing Allocator*
All return values checked via ResultUnchecked truncation or overrun

API Reference

IO_read

Result IO_read(Allocator* alloc, String path);

Summary: Reads the entire contents of a file into a dynamically allocated buffer. Uses the provided allocator for safe memory tracking.

  • Parameters:
    • alloc (Allocator*): The allocator used to provision the read buffer.
    • path (String): The file system path to read from.
  • Returns: Result containing OK with the file data payload, NIL if the file is empty, or ERR on system failure.
  • Errors: Returns ERR_FILE_ERROR if the file does not exist or permissions are denied.
  • See Also: IO_write
Example Usage
Result res = IO_read(arena, STRING("config.txt"));
if (res.status == RESULT_OK) {
    Slice data = res.payload;
    // Process data...
}

IO_write

Result IO_write(Allocator* alloc, String path, Slice data);

Summary: Safely writes a slice of bytes to a file. Overwrites the file if it exists or creates it if it does not.

  • Parameters:
    • alloc (Allocator*): The allocator used for any internal temporary buffers.
    • path (String): The destination file system path.
    • data (Slice): The byte payload to write to disk.
  • Returns: Result containing OK on success, or ERR on failure.
  • Errors: Returns ERR_FILE_ERROR on permission denial or I/O failure.
  • See Also: IO_read
Example Usage
String content = STRING("Hello, Camelot!");
Slice data = SLICE_FROM_STRING(content);
Result res = IO_write(arena, STRING("output.txt"), data);

STRING_format

[[nodiscard]] Result STRING_format(Allocator* alloc, const char* fmt, ...);

Summary: Allocator-aware string formatting utility. Acts as a structural alternative to asprintf, preventing double-frees and unmanaged memory.

  • Parameters:
    • alloc (Allocator*): The allocator to provision the resulting string.
    • fmt (const char*): A standard printf-style format string.
    • ...: Variadic arguments matching the format specifiers.
  • Returns: Result containing an OwnedString* payload on success.
  • Errors: Returns ERR_MEMORY_ERROR if allocation fails or ERR_FORMAT_ERROR on encoding issues.
  • See Also: OWNEDSTRING_deinit, STRING_formatVariadic
Example Usage
Result res = STRING_format(arena, "Failed to open %s (Code: %d)", path.ptr, err_code);
if (res.status == RESULT_OK) {
    OwnedString* str = res.payload;
    printf("%s\n", str->view.ptr);
    OWNEDSTRING_deinit(str);
}

STRING_formatVariadic

[[nodiscard]] Result STRING_formatVariadic(Allocator* alloc, const char* fmt, va_list args);

Summary: Variadic list variant of STRING_format. Useful for building custom logging or formatting wrappers.

  • Parameters:
    • alloc (Allocator*): The allocator to provision the resulting string.
    • fmt (const char*): A standard printf-style format string.
    • args (va_list): An initialized variadic argument list.
  • Returns: Result containing an OwnedString* payload on success.
  • See Also: STRING_format

OWNEDSTRING_deinit

void OWNEDSTRING_deinit(OwnedString* str);

Summary: Safely deallocates an OwnedString, returning its memory to the originating allocator.

  • Parameters:
    • str (OwnedString*): A pointer to the owned string to destroy. If NULL, the function is a no-op.
  • Side Effects: The memory underlying str->view is freed and the str struct itself is destroyed.
  • See Also: STRING_format

CI/CD Pipeline

The I/O module is validated through the full CI/CD pipeline defined in .github/workflows/ci.yml:

JobPurpose
buildCompile with full sanitizer flags (ASan, UBSan, LSan)
trivyVulnerability scanning on the filesystem
secret-scanGitleaks credential detection
sbomSPDX SBOM generation via Syft
scorecardOpenSSF Scorecard analysis
deps-devOSV dependency vulnerability scanning
deployAutomated GitHub Releases on v* tags

Release Deployment

When a version tag is pushed (git tag v1.0.0 && git push origin v1.0.0), the deploy job:

  1. Compiles the release binary with make all RELEASE=1
  2. Packages bin/camelot and include/ into camelot-linux-amd64.tar.gz
  3. Creates a GitHub Release with auto-generated release notes