Files
c3c/lib/std/io/os/file_libc.c3
Manu Linares eae7d0c4a1 stdlib: std::compression::zip and std::compression::deflate (#2930)
* stdlib: implement `std::compression::zip` and `std::compression::deflate`

- C3 implementation of DEFLATE (RFC 1951) and ZIP archive handling.
- Support for reading and writing archives using STORE and DEFLATE
methods.
- Decompression supports both fixed and dynamic Huffman blocks.
- Compression using greedy LZ77 matching.
- Zero dependencies on libc.
- Stream-based entry reading and writing.
- Full unit test coverage.

NOTE: This is an initial implementation. Future improvements could be:

- Optimization of the LZ77 matching (lazy matching).
- Support for dynamic Huffman blocks in compression.
- ZIP64 support for large files/archives.
- Support for encryption and additional compression methods.

* optimizations+refactoring

deflate:
- replace linear search with hash-based match finding.
- implement support for dynamic Huffman blocks using the Package-Merge
algorithm.
- add streaming decompression.
- add buffered StreamBitReader.

zip:
- add ZIP64 support.
- add CP437 and UTF-8 filename encoding detection.
- add DOS date/time conversion and timestamp preservation.
- add ZipEntryReader for streaming entry reads.
- implement ZipArchive.extract and ZipArchive.recover helpers.

other:
- Add `set_modified_time` to std::io;
- Add benchmarks and a few more unit tests.

* zip: add archive comment support

add tests

* forgot to rename the benchmark :(

* detect utf8 names on weird zips

fix method not passed to open_writer

* another edge case where directory doesn't end with /

* testing utilities

- detect encrypted zip
- `ZipArchive.open_writer` default to DEFLATE

* fix zip64 creation, add tests

* fix ZIP header endianness for big-endian compatibility

Update ZipLFH, ZipCDH, ZipEOCD, Zip64EOCD, and Zip64Locator structs to
use little-endian bitstruct types from std::core::bitorder

* fix ZipEntryReader position tracking and seek logic ZIP_METHOD_STORE

added a test to track this

* add package-merge algorithm attribution

Thanks @konimarti

* standalone deflate_benchmark.c3 against `miniz`

* fix integer overflows, leaks and improve safety

* a few safety for 32-bit systems and tests

* deflate compress optimization

* improve match finding, hash updates, and buffer usage

* use ulong for zip offsets

* style changes (#18)

* style changes

* update tests

* style changes in `deflate.c3`

* fix typo

* Allocator first. Some changes to deflate to use `copy_to`

* Fix missing conversion on 32 bits.

* Fix deflate stream. Formatting. Prefer switch over if-elseif

* - Stream functions now use long/ulong rather than isz/usz for seek/available.
- `instream.seek` is replaced by `set_cursor` and `cursor`.
- `instream.available`, `cursor` etc are long/ulong rather than isz/usz to be correct on 32-bit.

* Update to constdef

* Fix test

---------

Co-authored-by: Book-reader <thevoid@outlook.co.nz>
Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
2026-02-20 20:41:34 +01:00

145 lines
4.0 KiB
Plaintext

module std::io::os @if(env::LIBC);
import libc;
<*
@require mode.len > 0
@require filename.len > 0
*>
fn void*? native_fopen(String filename, String mode) @inline => @stack_mem(256; Allocator smem)
{
$if env::WIN32:
void* file = libc::_wfopen(filename.to_wstring(smem), mode.to_wstring(smem))!;
$else
void* file = libc::fopen(filename.zstr_copy(smem), mode.zstr_copy(smem));
$endif
return file ?: file_open_errno()~;
}
fn void? native_remove(String filename) => @stack_mem(256; Allocator smem)
{
$if env::WIN32:
CInt result = libc::_wremove(filename.to_wstring(smem))!;
$else
CInt result = libc::remove(filename.zstr_copy(smem));
$endif
if (result)
{
switch (libc::errno())
{
case errno::ENOENT:
return io::FILE_NOT_FOUND~;
case errno::EACCES:
default:
return io::FILE_CANNOT_DELETE~;
}
}
}
<*
@require mode.len > 0
@require filename.len > 0
*>
fn void*? native_freopen(void* file, String filename, String mode) @inline => @stack_mem(256; Allocator smem)
{
$if env::WIN32:
file = libc::_wfreopen(filename.to_wstring(smem), mode.to_wstring(smem), file)!;
$else
file = libc::freopen(filename.zstr_copy(smem), mode.zstr_copy(smem), file);
$endif
return file ?: file_open_errno()~;
}
fn void? native_fseek(void* file, long offset, SeekOrigin seek_mode) @inline
{
if (libc::fseek(file, (SeekIndex)offset, seek_mode.ordinal)) return file_seek_errno()~;
}
fn long? native_ftell(CFile file) @inline
{
long index = libc::ftell(file);
return index >= 0 ? index : file_seek_errno()~;
}
fn usz? native_fwrite(CFile file, char[] buffer) @inline
{
return libc::fwrite(buffer.ptr, 1, buffer.len, file);
}
fn void? native_fputc(CInt c, CFile stream) @inline
{
if (libc::fputc(c, stream) == libc::EOF) return io::EOF~;
}
fn usz? native_fread(CFile file, char[] buffer) @inline
{
return libc::fread(buffer.ptr, 1, buffer.len, file);
}
macro fault file_open_errno() @local
{
switch (libc::errno())
{
case errno::EACCES: return io::NO_PERMISSION;
case errno::EDQUOT: return io::OUT_OF_SPACE;
case errno::EBADF: return io::FILE_NOT_VALID;
case errno::EEXIST: return io::ALREADY_EXISTS;
case errno::EINTR: return io::INTERRUPTED;
case errno::EFAULT: return io::GENERAL_ERROR;
case errno::EISDIR: return io::FILE_IS_DIR;
case errno::ELOOP: return io::SYMLINK_FAILED;
case errno::EMFILE: return io::TOO_MANY_DESCRIPTORS;
case errno::ENAMETOOLONG: return io::NAME_TOO_LONG;
case errno::ENFILE: return io::OUT_OF_SPACE;
case errno::ENOTDIR: return io::FILE_NOT_DIR;
case errno::ENOENT: return io::FILE_NOT_FOUND;
case errno::ENOSPC: return io::OUT_OF_SPACE;
case errno::ENXIO: return io::FILE_NOT_FOUND;
case errno::EOVERFLOW: return io::OVERFLOW;
case errno::EROFS: return io::READ_ONLY;
case errno::EOPNOTSUPP: return io::UNSUPPORTED_OPERATION;
case errno::EIO: return io::INCOMPLETE_WRITE;
case errno::EWOULDBLOCK: return io::WOULD_BLOCK;
default: return io::UNKNOWN_ERROR;
}
}
macro fault file_seek_errno() @local
{
switch (libc::errno())
{
case errno::ESPIPE: return io::FILE_IS_PIPE;
case errno::EPIPE: return io::FILE_IS_PIPE;
case errno::EOVERFLOW: return io::OVERFLOW;
case errno::ENXIO: return io::FILE_NOT_FOUND;
case errno::ENOSPC: return io::OUT_OF_SPACE;
case errno::EIO: return io::INCOMPLETE_WRITE;
case errno::EINVAL: return io::INVALID_POSITION;
case errno::EINTR: return io::INTERRUPTED;
case errno::EFBIG: return io::OUT_OF_SPACE;
case errno::EBADF: return io::FILE_NOT_VALID;
case errno::EAGAIN: return io::WOULD_BLOCK;
default: return io::UNKNOWN_ERROR;
}
}
struct Utimbuf
{
Time_t actime;
Time_t modtime;
}
extern fn int utime(char* filename, void* times) @if(!env::WIN32);
extern fn int _wutime(WChar* filename, void* times) @if(env::WIN32);
fn void? native_set_modified_time(String filename, libc::Time_t time) => @stack_mem(256; Allocator smem)
{
Utimbuf times = { time, time };
$if env::WIN32:
if (_wutime(filename.to_wstring(smem)!, &times)) return io::GENERAL_ERROR~;
$else
if (utime(filename.zstr_copy(smem), &times)) return io::GENERAL_ERROR~;
$endif
}