commit 90fb68940b7b2c56b21905a81e57ebed5226666d Author: Edgar Su Date: Sun Oct 10 14:11:45 2021 +0800 Initial commit diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..ceb652a --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: ["-I/usr/include/efi", "-I/usr/include/efi/x86_64", "-DHELOS"] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73a70be --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Compiled result +Main.efi +*.o + +# Test files compiled result +a.out + +# macOS trash +.DS_Store +# vscode trash +.vscode +# Dolphin trash +.directory + + diff --git a/Linker.ld b/Linker.ld new file mode 100644 index 0000000..b9d9428 --- /dev/null +++ b/Linker.ld @@ -0,0 +1,199 @@ +/* Default linker script, for normal executables */ +/* Copyright (C) 2014-2020 Free Software Foundation, Inc. + Copying and distribution of this script, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. */ +OUTPUT_FORMAT(pei-x86-64) +SEARCH_DIR("/usr/x86_64-w64-mingw32/lib"); + +__section_alignment__ = 4096; + +SECTIONS +{ + /* Make the virtual address and file offset synced if the alignment is + lower than the target page size. */ + . = SIZEOF_HEADERS; + . = ALIGN(__section_alignment__); + .text __image_base__ + ( __section_alignment__ < 0x1000 ? . : __section_alignment__ ) : + { + PROVIDE(link_TextStart = .); + KEEP (*(SORT_NONE(.init))) + *(.text) + *(SORT(.text$*)) + *(.text.*) + *(.gnu.linkonce.t.*) + *(.glue_7t) + *(.glue_7) + . = ALIGN(8); + /* Note: we always define __CTOR_LIST__ and ___CTOR_LIST__ here, + we do not PROVIDE them. This is because the ctors.o startup + code in libgcc defines them as common symbols, with the + expectation that they will be overridden by the definitions + here. If we PROVIDE the symbols then they will not be + overridden and global constructors will not be run. + See PR 22762 for more details. + + This does mean that it is not possible for a user to define + their own __CTOR_LIST__ and __DTOR_LIST__ symbols; if they do, + the content from those variables are included but the symbols + defined here silently take precedence. If they truly need to + be redefined, a custom linker script will have to be used. + (The custom script can just be a copy of this script with the + PROVIDE() qualifiers added). + In particular this means that ld -Ur does not work, because + the proper __CTOR_LIST__ set by ld -Ur is overridden by a + bogus __CTOR_LIST__ set by the final link. See PR 46. */ + ___CTOR_LIST__ = .; + __CTOR_LIST__ = .; + LONG (-1); LONG (-1); + KEEP (*(.ctors)); + KEEP (*(.ctor)); + KEEP (*(SORT_BY_NAME(.ctors.*))); + LONG (0); LONG (0); + /* See comment about __CTOR_LIST__ above. The same reasoning + applies here too. */ + ___DTOR_LIST__ = .; + __DTOR_LIST__ = .; + LONG (-1); LONG (-1); + KEEP (*(.dtors)); + KEEP (*(.dtor)); + KEEP (*(SORT_BY_NAME(.dtors.*))); + LONG (0); LONG (0); + KEEP (*(SORT_NONE(.fini))) + /* ??? Why is .gcc_exc here? */ + *(.gcc_exc) + PROVIDE (etext = .); + PROVIDE(link_TextEnd = .); + KEEP (*(.gcc_except_table)) + } + /* The Cygwin32 library uses a section to avoid copying certain data + on fork. This used to be named ".data". The linker used + to include this between __data_start__ and __data_end__, but that + breaks building the cygwin32 dll. Instead, we name the section + ".data_cygwin_nocopy" and explicitly include it after __data_end__. */ + .data BLOCK(__section_alignment__) : + { + PROVIDE(link_DataStart = .); + __data_start__ = . ; + *(.data) + *(.data2) + *(SORT(.data$*)) + KEEP(*(.jcr)) + __data_end__ = . ; + *(.data_cygwin_nocopy) + } + .idata BLOCK(__section_alignment__) : + { + /* This cannot currently be handled with grouped sections. + See pep.em:sort_sections. */ + KEEP (SORT(*)(.idata$2)) + KEEP (SORT(*)(.idata$3)) + /* These zeroes mark the end of the import list. */ + LONG (0); LONG (0); LONG (0); LONG (0); LONG (0); + KEEP (SORT(*)(.idata$4)) + __IAT_start__ = .; + SORT(*)(.idata$5) + __IAT_end__ = .; + KEEP (SORT(*)(.idata$6)) + KEEP (SORT(*)(.idata$7)) + PROVIDE(link_DataEnd = .); + } + .rdata BLOCK(__section_alignment__) : + { + PROVIDE(link_RodataStart = .); + *(.rdata) + *(SORT(.rdata$*)) + *(.rodata) + . = ALIGN(4); + __rt_psrelocs_start = .; + KEEP(*(.rdata_runtime_pseudo_reloc)) + __rt_psrelocs_end = .; + PROVIDE(link_RodataEnd = .); + } + __rt_psrelocs_size = __rt_psrelocs_end - __rt_psrelocs_start; + ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .; + __RUNTIME_PSEUDO_RELOC_LIST_END__ = .; + ___RUNTIME_PSEUDO_RELOC_LIST__ = . - __rt_psrelocs_size; + __RUNTIME_PSEUDO_RELOC_LIST__ = . - __rt_psrelocs_size; + .eh_frame BLOCK(__section_alignment__) : + { + KEEP (*(.eh_frame*)) + } + .pdata BLOCK(__section_alignment__) : + { + KEEP(*(.pdata*)) + } + .xdata BLOCK(__section_alignment__) : + { + KEEP(*(.xdata*)) + } + .edata BLOCK(__section_alignment__) : + { + *(.edata) + } + .bss BLOCK(__section_alignment__) : + { + PROVIDE(link_BssStart = .); + __bss_start__ = . ; + *(.bss) + *(COMMON) + __bss_end__ = . ; + PROVIDE(link_BssEnd = .); + } + /DISCARD/ : + { + *(.debug$S) + *(.debug$T) + *(.debug$F) + *(.drectve) + *(.note.GNU-stack) + *(.gnu.lto_*) + } + .CRT BLOCK(__section_alignment__) : + { + ___crt_xc_start__ = . ; + KEEP (*(SORT(.CRT$XC*))) /* C initialization */ + ___crt_xc_end__ = . ; + ___crt_xi_start__ = . ; + KEEP (*(SORT(.CRT$XI*))) /* C++ initialization */ + ___crt_xi_end__ = . ; + ___crt_xl_start__ = . ; + KEEP (*(SORT(.CRT$XL*))) /* TLS callbacks */ + /* ___crt_xl_end__ is defined in the TLS Directory support code */ + ___crt_xp_start__ = . ; + KEEP (*(SORT(.CRT$XP*))) /* Pre-termination */ + ___crt_xp_end__ = . ; + ___crt_xt_start__ = . ; + KEEP (*(SORT(.CRT$XT*))) /* Termination */ + ___crt_xt_end__ = . ; + } + /* Windows TLS expects .tls$AAA to be at the start and .tls$ZZZ to be + at the end of the .tls section. This is important because _tls_start MUST + be at the beginning of the section to enable SECREL32 relocations with TLS + data. */ + .tls BLOCK(__section_alignment__) : + { + ___tls_start__ = . ; + KEEP (*(.tls$AAA)) + KEEP (*(.tls)) + KEEP (*(.tls$)) + KEEP (*(SORT(.tls$*))) + KEEP (*(.tls$ZZZ)) + ___tls_end__ = . ; + } + .endjunk BLOCK(__section_alignment__) : + { + /* end is deprecated, don't use it */ + PROVIDE (end = .); + PROVIDE ( _end = .); + __end__ = .; + } + .reloc BLOCK(__section_alignment__) : + { + PROVIDE(link_RelocStart = .); + *(.reloc) + PROVIDE(link_RelocEnd = .); + } +} + + diff --git a/Linker.ld.old b/Linker.ld.old new file mode 100644 index 0000000..056c7eb --- /dev/null +++ b/Linker.ld.old @@ -0,0 +1,62 @@ + +OUTPUT_FORMAT(pei-x86-64) +SEARCH_DIR("/usr/x86_64-w64-mingw32/lib"); + +/* Tell where the various sections of the object files will be put in the final + kernel image. */ +SECTIONS +{ + /* Make the virtual address and file offset synced if the alignment is + lower than the target page size. */ + . = SIZEOF_HEADERS; + . = ALIGN(__section_alignment__); + + /* First put the multiboot header, as it is required to be put very early + early in the image or the bootloader won't recognize the file format. + Next we'll put the .text section. */ + .text BLOCK(4K) : ALIGN(4K) + { + PROVIDE(link_TextStart = .); + /* *(.multiboot) */ + *(.text) + PROVIDE(link_TextEnd = .); + } + + /* Read-only data. */ + .rdata BLOCK(4K) : ALIGN(4K) + { + PROVIDE(link_RodataStart = .); + /* *(.rodata) */ /* as in ELF/SysV style */ + *(.rdata) + *(SORT(.rdata$*)) + . = ALIGN(4); + __rt_psrelocs_start = .; + KEEP(*(.rdata_runtime_pseudo_reloc)) + __rt_psrelocs_end = .; + PROVIDE(link_RodataEnd = .); + } + + /* Read-write data (initialized) */ + .data BLOCK(4K) : ALIGN(4K) + { + PROVIDE(link_DataStart = .); + *(.data) + *(.data) + *(.data2) + *(SORT(.data$*)) + KEEP(*(.jcr)) + PROVIDE(link_DataEnd = .); + } + + /* Read-write data (uninitialized) and stack */ + .bss BLOCK(4K) : ALIGN(4K) + { + PROVIDE(link_BssStart = .); + *(COMMON) + *(.bss) + PROVIDE(link_BssEnd = .); + } + + /* The compiler may produce other sections, by default it will put them in + a segment with the same name. Simply add stuff here as needed. */ +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..006f744 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ + +.SILENT: +include Makefile.flags + + +objects = $(patsubst %.c,%.o,$(shell find . -name "*.c")) $(patsubst %.cpp,%.o,$(shell find . -name "*.cpp")) +objects_fasm = $(patsubst %.S,%.o,$(shell find . -name "*.S")) + +objects_test = $(patsubst %.c,%.o,$(shell find . -name "test_*.c")) $(patsubst %.cpp,%.o,$(shell find . -name "test_*.cpp")) +objects := $(filter-out $(objects_test),$(objects)) + + +all: Main.efi + + +Main.efi: $(objects) $(objects_fasm) + $(LD) $(LDFLAGS) $(objects) $(objects_fasm) $(LDLIBS) + + +clean: + echo " -RM Main.efi $(objects)" + -$(RM) -f Main.efi $(objects) $(objects_fasm) + +install: Main.efi + echo " CP Main.efi ../FAT/EFI/Boot/bootx64.efi" + $(CP) Main.efi ../FAT/EFI/Boot/bootx64.efi + +upload: Main.efi + echo " SCP Main.efi router.edgaru089.ml:/opt/tftp/" + scp -P 29657 Main.efi root@router.edgaru089.ml:/opt/tftp/ + +.PHONY: all clean install upload + diff --git a/Makefile.flags b/Makefile.flags new file mode 100644 index 0000000..b445bad --- /dev/null +++ b/Makefile.flags @@ -0,0 +1,28 @@ + +.SILENT: + + +FLAGS_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +SELF_DIR = $(dir $@) + +export RM = rm +export CP = cp + +export AR = echo " AR $<" && x86_64-w64-mingw32-ar +export AS = echo " AS $^" && x86_64-w64-mingw32-as +export CC = echo " CC $^" && x86_64-w64-mingw32-gcc +export CXX = echo " CXX $^" && x86_64-w64-mingw32-g++ +export LD = echo " LD $@" && x86_64-w64-mingw32-gcc +export FASM = echo " FASM $^" && fasm >/dev/null + +export INCLUDEFLAGS = -I/usr/include/efi -I/usr/include/efi/x86_64 + +export CPPFLAGS = +export CFLAGS = $(INCLUDEFLAGS) -DHELOS -O2 -Wno-attributes -fPIE -ffreestanding -nostdlib -mcmodel=large -mno-red-zone +export CXXFLAGS = $(INCLUDEFLAGS) -DHELOS -O2 -Wno-unused-result -std=c++17 -fPIE -ffreestanding -nostdlib -mcmodel=large -mno-red-zone -fno-exceptions -fno-rtti +export LDFLAGS = -T Linker.ld -O2 -eefiMain -nostdlib -shared -fPIE -ffreestanding -Wl,--dynamicbase,--subsystem,10 -o Main.efi -s +export LDLIBS = ../Unifont/unifont.o -lgcc + +# Pattern rule for FASM assembly +%.o: %.S + $(FASM) $^ $@ diff --git a/Makefile.subdir b/Makefile.subdir new file mode 100644 index 0000000..a829280 --- /dev/null +++ b/Makefile.subdir @@ -0,0 +1,19 @@ + +SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +objects = $(patsubst %.c,%.o,$(wildcard *.c)) $(patsubst %.cpp,%.o,$(wildcard *.cpp)) +objects_fasm = $(patsubst %.S,%.o,$(wildcard *.S)) + +objects_test = $(patsubst %.c,%.o,$(wildcard test_*.c)) $(patsubst %.cpp,%.o,$(wildcard test_*.cpp)) +objects := $(filter-out $(objects_test),$(objects)) + + +all: $(objects) $(objects_fasm) + +$(objects_fasm): + $(FASM) $(patsubst %.o,%.S,$@) $@ + +clean: + -$(RM) *.o + + diff --git a/execformat/pe/format.h b/execformat/pe/format.h new file mode 100644 index 0000000..522427e --- /dev/null +++ b/execformat/pe/format.h @@ -0,0 +1,256 @@ +#pragma once + +#include "../../main.h" +#include "../../runtime/panic_assert.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// PE Header Magic, comes after the MS-DOS stub and right before PE Header +#define EXECFORMAT_PE_HEADER_MAGIC "PE\0" // comes with a \0 itself + +// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format +typedef struct { + uint16_t machineType; + uint16_t numSections; + uint32_t unixTimestamp; + uint32_t offsetSymbolTable; + uint32_t numSymbols; + uint16_t sizeofOptionalHeader; + uint16_t flags; // "Characteristics" +} PACKED execformat_pe_Header; + +// Machine types +#define EXECFORMAT_PE_MACHINE_UNKNOWN 0x0u // The content of this field is assumed to be applicable to any machine type +#define EXECFORMAT_PE_MACHINE_AMD64 0x8664u // x64 +#define EXECFORMAT_PE_MACHINE_I386 0x14cu // Intel 386 or later processors and compatible processors +#define EXECFORMAT_PE_MACHINE_EBC 0xebcu // EFI byte code + +// Characteristics +#define EXECFORMAT_PE_FLAG_RELOCS_STRIPPED 0x0001u // base relocs removed and must be loaded at its preferred base address. +#define EXECFORMAT_PE_FLAG_EXECUTABLE_IMAGE 0x0002u // the image file is valid and can be run. +#define EXECFORMAT_PE_FLAG_LINE_NUMS_STRIPPED 0x0004u // COFF line numbers removed. deprecated and should be zero. +#define EXECFORMAT_PE_FLAG_LOCAL_SYMS_STRIPPED 0x0008u // COFF symbol table entries for local symbols removed. deprecated and should be zero. +#define EXECFORMAT_PE_FLAG_AGGRESSIVE_WS_TRIM 0x0010u // Aggressively trim working set. deprecated and later and must be zero. +#define EXECFORMAT_PE_FLAG_LARGE_ADDRESS_AWARE 0x0020u // Application can handle >2GB addresses. +#define EXECFORMAT_PE_FLAG_RESERVED_0x0040 0x0040u +#define EXECFORMAT_PE_FLAG_BYTES_REVERSED_LO 0x0080u // Little endian. This flag is deprecated and should be zero. +#define EXECFORMAT_PE_FLAG_32BIT_MACHINE 0x0100u // Machine is based on a 32-bit-word architecture. +#define EXECFORMAT_PE_FLAG_DEBUG_STRIPPED 0x0200u // Debugging information is removed from the image file. +#define EXECFORMAT_PE_FLAG_REMOVABLE_RUN_FROM_SWAP 0x0400u // If the image is on removable media, fully load it and copy it to the swap file. +#define EXECFORMAT_PE_FLAG_NET_RUN_FROM_SWAP 0x0800u // If the image is on network media, fully load it and copy it to the swap file. +#define EXECFORMAT_PE_FLAG_SYSTEM 0x1000u // The image file is a system file, not a user program. +#define EXECFORMAT_PE_FLAG_DLL 0x2000u // The image file is a dynamic-link library (DLL). +#define EXECFORMAT_PE_FLAG_UP_SYSTEM_ONLY 0x4000u // The file should be run only on a uniprocessor machine. +#define EXECFORMAT_PE_FLAG_BYTES_REVERSED_HI 0x8000u // Big endian. This flag is deprecated and should be zero. + +// Optional Header Magic +#define EXECFORMAT_PE_OPTIONAL_HEADER_MAGIC_PE32 0x10bu // 2-byte magic for PE32 Optional Header, right following the PE Header. +#define EXECFORMAT_PE_OPTIONAL_HEADER_MAGIC_PE32P 0x20bu // 2-byte magic for PE32+ Optional Header, right following the PE Header. + +typedef struct { + uint16_t magic; // either 0x10b for PE32, or 0x20b for PE32+ + uint8_t majorLinkerVersion, minorLinkerVersion; + uint32_t sizeofText; // size of the .text section, or the sum of all text sections + uint32_t sizeofData; // size of initialized data sections + uint32_t sizeofBss; // size of uninitialized data + uint32_t offsetofEntryPoint; // offset of the entry point (starting address) relative to the image base + uint32_t offsetofText; // offset of the start of .text section relative to the image base +} PACKED execformat_pe_OptionalHeader_StandardFields_PE32P; + +typedef struct { + uint16_t magic; // either 0x10b for PE32, or 0x20b for PE32+ + uint8_t majorLinkerVersion, minorLinkerVersion; + uint32_t sizeofText; // size of the .text section, or the sum of all text sections + uint32_t sizeofData; // size of initialized data sections + uint32_t sizeofBss; // size of uninitialized data + uint32_t offsetofEntryPoint; // offset of the entry point (starting address) relative to the image base + uint32_t offsetofText; // offset of the start of .text section relative to the image base + uint32_t offsetofData; // only present in PE32 images +} PACKED execformat_pe_OptionalHeader_StandardFields_PE32; + +// PE32+ struct with a 64-bit ImageBase +typedef struct { + uint64_t imageBase; // The preferred address of the first byte of image when loaded into memory; must be aligned to 64K. + uint32_t sectionAlignment; // The alignment (in bytes) of sections when they are loaded into memory. + uint32_t fileAlignment; // The alignment factor (in bytes) that is used to align the raw data of sections in the image file. + uint16_t majorOSVersion, minorOSVersion; // The version number of the required operating system. + uint16_t majorImageVersion, minorImageVersion; + uint16_t majorSubsystemVersion, minorSubsystemVersion; + uint32_t win32VersionValue; // Reserved, must be 0 + uint32_t sizeofImage; // Size in bytes of the image, including all headers as the image is loaded into memory. + uint32_t sizeofHeaders; // Combined size of the MS-DOS stub, PE header, and section headers rounded up to FileAlignment. + uint32_t checksum; // Checksum of an unknown(???) algorithm + uint16_t subsystem; // Subsystem + uint16_t dllFlags; // DLL Characteristics + uint64_t sizeofStackReserve, sizeofStackCommit; + uint64_t sizeofHeapReserve, sizeofHeapCommit; + uint32_t loaderFlags; // Reserved, must be 0 + uint32_t numRVAandSizes; // The number of data-directory entries in the remainder of the optional header. Each describes a location and size. +} PACKED execformat_pe_OptionalHeader_WindowsFields_PE32P; + +// PE32 struct with a 32-bit ImageBase +typedef struct { + uint32_t imageBase; // The preferred address of the first byte of image when loaded into memory; must be aligned to 64K. + uint32_t sectionAlignment; // The alignment (in bytes) of sections when they are loaded into memory. + uint32_t fileAlignment; // The alignment factor (in bytes) that is used to align the raw data of sections in the image file. + uint16_t majorOSVersion, minorOSVersion; // The version number of the required operating system. + uint16_t majorImageVersion, minorImageVersion; + uint16_t majorSubsystemVersion, minorSubsystemVersion; + uint32_t win32VersionValue; // Reserved, must be 0 + uint32_t sizeofImage; // Size in bytes of the image, including all headers as the image is loaded into memory. + uint32_t sizeofHeaders; // Combined size of the MS-DOS stub, PE header, and section headers rounded up to FileAlignment. + uint32_t checksum; // Checksum of an unknown(???) algorithm + uint16_t subsystem; // Subsystem + uint16_t dllFlags; // DLL Characteristics + uint32_t sizeofStackReserve, sizeofStackCommit; + uint32_t sizeofHeapReserve, sizeofHeapCommit; + uint32_t loaderFlags; // Reserved, must be 0 + uint32_t numRVAandSizes; // The number of data-directory entries in the remainder of the optional header. Each describes a location and size. +} PACKED execformat_pe_OptionalHeader_WindowsFields_PE32; + +// Subsystems +#define EXECFORMAT_PE_SUBSYSTEM_UNKNOWN 0 +#define EXECFORMAT_PE_SUBSYSTEM_NATIVE 1 // Device drivers and native Windows processes +#define EXECFORMAT_PE_SUBSYSTEM_WINDOWS_GUI 2 // Windows graphical user interface (GUI) subsystem +#define EXECFORMAT_PE_SUBSYSTEM_WINDOWS_CUI 3 // Windows character subsystem +#define EXECFORMAT_PE_SUBSYSTEM_OS2_CUI 5 // OS/2 character subsystem +#define EXECFORMAT_PE_SUBSYSTEM_POSIX_CUI 7 // POSIX character subsystem +#define EXECFORMAT_PE_SUBSYSTEM_NATIVE_WINDOWS 8 // Native Win9x driver +#define EXECFORMAT_PE_SUBSYSTEM_WINDOWS_CE_GUI 9 // Windows CE +#define EXECFORMAT_PE_SUBSYSTEM_EFI_APPLICATION 10 // Extensible Firmware Interface (EFI) application +#define EXECFORMAT_PE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER 11 // EFI Boot Services driver +#define EXECFORMAT_PE_SUBSYSTEM_EFI_RUNTIME_DRIVER 12 // EFI Runtime driver +#define EXECFORMAT_PE_SUBSYSTEM_EFI_ROM 13 // EFI ROM image +#define EXECFORMAT_PE_SUBSYSTEM_XBOX 14 // XBOX +#define EXECFORMAT_PE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION 16 // Windows boot application + +// DLL Characteristics +#define EXECFORMAT_PE_DLLFLAG_RESERVED_0x0001 0x0001u // Reserved, must be 0 +#define EXECFORMAT_PE_DLLFLAG_RESERVED_0x0002 0x0002u // Reserved, must be 0 +#define EXECFORMAT_PE_DLLFLAG_RESERVED_0x0004 0x0004u // Reserved, must be 0 +#define EXECFORMAT_PE_DLLFLAG_RESERVED_0x0008 0x0008u // Reserved, must be 0 +#define EXECFORMAT_PE_DLLFLAG_HIGH_ENTROPY_VA 0x0020u // Image can handle a high entropy 64-bit virtual address space. +#define EXECFORMAT_PE_DLLFLAG_DYNAMIC_BASE 0x0040u // DLL can be relocated at load time. +#define EXECFORMAT_PE_DLLFLAG_FORCE_INTEGRITY 0x0080u // Code Integrity checks are enforced. +#define EXECFORMAT_PE_DLLFLAG_NX_COMPAT 0x0100u // Image is NX compatible. +#define EXECFORMAT_PE_DLLFLAG_NO_ISOLATION 0x0200u // Isolation aware, but do not isolate the image. +#define EXECFORMAT_PE_DLLFLAG_NO_SEH 0x0400u // Does not use structured exception (SE) handling. +#define EXECFORMAT_PE_DLLFLAG_NO_BIND 0x0800u // Do not bind the image. +#define EXECFORMAT_PE_DLLFLAG_APPCONTAINER 0x1000u // Image must execute in an AppContainer. +#define EXECFORMAT_PE_DLLFLAG_WDM_DRIVER 0x2000u // A WDM driver. +#define EXECFORMAT_PE_DLLFLAG_GUARD_CF 0x4000u // Image supports Control Flow Guard. +#define EXECFORMAT_PE_DLLFLAG_TERMINAL_SERVER_AWARE 0x8000u // Terminal Server aware. + +typedef struct { + uint32_t Offset; + uint32_t Size; +} PACKED execformat_pe_OptionalHeader_DataDirectory; + +// Data Directory entry indexes +#define EXECFORMAT_PE_DATADIR_INDEX_EXPORT 0 // .edata +#define EXECFORMAT_PE_DATADIR_INDEX_IMPORT 1 // .idata +#define EXECFORMAT_PE_DATADIR_INDEX_RESOURCE 2 // .rsrc +#define EXECFORMAT_PE_DATADIR_INDEX_EXCEPTION 3 // .pdata +#define EXECFORMAT_PE_DATADIR_INDEX_CERTIFICATE 4 +#define EXECFORMAT_PE_DATADIR_INDEX_BASE_RELOCATION 5 // .reloc +#define EXECFORMAT_PE_DATADIR_INDEX_DEBUG 6 // .debug +#define EXECFORMAT_PE_DATADIR_INDEX_ARCHITECTURE 7 // Reserved, must be 0 +#define EXECFORMAT_PE_DATADIR_INDEX_GLOBAL_PTR 8 // The RVA of the value to be stored in the global pointer register. The size must be 0. +#define EXECFORMAT_PE_DATADIR_INDEX_TLS 9 // .tls +#define EXECFORMAT_PE_DATADIR_INDEX_LOAD_CONFIG 10 +#define EXECFORMAT_PE_DATADIR_INDEX_BOUND_IMPORT 11 +#define EXECFORMAT_PE_DATADIR_INDEX_IAT 12 // Import Address Table +#define EXECFORMAT_PE_DATADIR_INDEX_DELAY_IMPORT_DESC 13 // Delay Import Descriptor +#define EXECFORMAT_PE_DATADIR_INDEX_CLR_RUNTIME_HEADER 14 // Common Language Runtime header +#define EXECFORMAT_PE_DATADIR_INDEX_RESERVED_15 15 // Reserved, must be 0 + +typedef struct { + execformat_pe_OptionalHeader_StandardFields_PE32P std; + execformat_pe_OptionalHeader_WindowsFields_PE32P win; + execformat_pe_OptionalHeader_DataDirectory data[1]; +} PACKED execformat_pe_OptionalHeader_PE32P; + +typedef struct { + execformat_pe_OptionalHeader_StandardFields_PE32 std; + execformat_pe_OptionalHeader_WindowsFields_PE32 win; + execformat_pe_OptionalHeader_DataDirectory data[1]; +} PACKED execformat_pe_OptionalHeader_PE32; + +static inline void execformat_pe_OptionalHeader_CheckPacking() { + assert(sizeof(execformat_pe_OptionalHeader_StandardFields_PE32) == 28); + assert(sizeof(execformat_pe_OptionalHeader_StandardFields_PE32P) == 24); + assert(sizeof(execformat_pe_OptionalHeader_WindowsFields_PE32) == 68); + assert(sizeof(execformat_pe_OptionalHeader_WindowsFields_PE32P) == 88); + + assert(offsetof(execformat_pe_OptionalHeader_PE32, std) == 0); + assert(offsetof(execformat_pe_OptionalHeader_PE32, win) == 28); + assert(offsetof(execformat_pe_OptionalHeader_PE32, data[0]) == 96); + assert(offsetof(execformat_pe_OptionalHeader_PE32P, std) == 0); + assert(offsetof(execformat_pe_OptionalHeader_PE32P, win) == 24); + assert(offsetof(execformat_pe_OptionalHeader_PE32P, data[0]) == 112); +} + + +typedef struct { + char name[8]; + uint32_t virtualSize; + uint32_t virtualAddr; // Offset(RVA) instead of a real Virtual Address + uint32_t sizeofRawData; + uint32_t pointerToRawData; // The file pointer to the first page of the section within the file. + uint32_t pointerToRelocations; + uint32_t pointerToLineNumbers; + uint16_t numRelocations; + uint16_t numLineNumbers; // deprecated, should be 0 + uint32_t flags; // The flags that describe the characteristics of the section. +} PACKED execformat_pe_SectionHeader; + +// Section Characteristics +#define EXECFORMAT_PE_SECTIONFLAG_NO_PAD 0x00000008u // section should not be padded to the next boundary, obsolete. +#define EXECFORMAT_PE_SECTIONFLAG_CNT_CODE 0x00000020u // section contains executable code. +#define EXECFORMAT_PE_SECTIONFLAG_CNT_INITIALIZED_DATA 0x00000040u // section contains initialized data. +#define EXECFORMAT_PE_SECTIONFLAG_CNT_UNINITIALIZED_DATA 0x00000080u // section contains uninitialized data. +#define EXECFORMAT_PE_SECTIONFLAG_LNK_OTHER 0x00000100u // Reserved. +#define EXECFORMAT_PE_SECTIONFLAG_LNK_INFO 0x00000200u // section contains comments or other info. .drectve section has this type. +#define EXECFORMAT_PE_SECTIONFLAG_LNK_REMOVE 0x00000800u // section will not become part of the image. valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_LNK_COMDAT 0x00001000u // The section contains COMDAT data. valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_GPREL 0x00008000u // section contains data referenced through the global pointer (GP). +#define EXECFORMAT_PE_SECTIONFLAG_MEM_PURGEABLE 0x00020000u // Reserved. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_16BIT 0x00020000u // Reserved. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_LOCKED 0x00040000u // Reserved. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_PRELOAD 0x00080000u // Reserved. + +// Section Characteristics - Alignment (only for object files) +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_1BYTES 0x00100000u // Align data on a 1-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_2BYTES 0x00200000u // Align data on a 2-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_4BYTES 0x00300000u // Align data on a 4-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_8BYTES 0x00400000u // Align data on a 8-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_16BYTES 0x00500000u // Align data on a 16-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_32BYTES 0x00600000u // Align data on a 32-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_64BYTES 0x00700000u // Align data on a 64-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_128BYTES 0x00800000u // Align data on a 128-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_256BYTES 0x00900000u // Align data on a 256-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_512BYTES 0x00a00000u // Align data on a 512-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_1024BYTES 0x00b00000u // Align data on a 1024-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_2048BYTES 0x00c00000u // Align data on a 2048-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_4096BYTES 0x00d00000u // Align data on a 4096-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_8192BYTES 0x00e00000u // Align data on a 8192-byte boundary. Valid only for object files. +#define EXECFORMAT_PE_SECTIONFLAG_ALIGN_MASK 0x00f00000u // Mask for section data alignment type. + +// Section Characteristics - Memory +#define EXECFORMAT_PE_SECTIONFLAG_LNK_NRELOC_OVFL 0x01000000u // section contains extended relocations. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_DISCARDABLE 0x02000000u // section can be discarded as needed. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_NOT_CACHED 0x04000000u // section cannot be cached. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_NOT_PAGED 0x08000000u // section cannot be paged out. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_SHARED 0x10000000u // section can be shared in memory. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_EXECUTE 0x20000000u // section can be executed as code. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_READ 0x40000000u // section can be read. +#define EXECFORMAT_PE_SECTIONFLAG_MEM_WRITE 0x80000000u // section can be written to. + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/execformat/pe/read_headers.c b/execformat/pe/read_headers.c new file mode 100644 index 0000000..868f7e0 --- /dev/null +++ b/execformat/pe/read_headers.c @@ -0,0 +1,106 @@ + +#include "format.h" +#include "struct.h" +#include "../../memory/memory.h" + +#include + +static const char *caseMachine(uint16_t type) { +#define CASE(expr) \ + case EXECFORMAT_PE_##expr: \ + return #expr; + + switch (type) { + CASE(MACHINE_UNKNOWN) + CASE(MACHINE_AMD64) + CASE(MACHINE_I386) + CASE(MACHINE_EBC) + default: + return "(Unknown)"; + } + +#undef CASE +} + +static void caseFlags(uint32_t flags) { +#define CASE(expr) \ + if (flags & EXECFORMAT_PE_FLAG_##expr) { \ + printf("|" #expr); \ + } + CASE(RELOCS_STRIPPED) + CASE(EXECUTABLE_IMAGE) + CASE(LINE_NUMS_STRIPPED) + CASE(LOCAL_SYMS_STRIPPED) + CASE(AGGRESSIVE_WS_TRIM) + CASE(LARGE_ADDRESS_AWARE) + CASE(RESERVED_0x0040) + CASE(BYTES_REVERSED_LO) + CASE(32BIT_MACHINE) + CASE(DEBUG_STRIPPED) + CASE(REMOVABLE_RUN_FROM_SWAP) + CASE(NET_RUN_FROM_SWAP) + CASE(SYSTEM) + CASE(DLL) + CASE(UP_SYSTEM_ONLY) + CASE(BYTES_REVERSED_HI) + printf("|\n"); +#undef CASE +} +static void caseSectionFlags(uint32_t flags) { +#define CASE(expr) \ + if (flags & EXECFORMAT_PE_SECTIONFLAG_##expr) { \ + printf("|" #expr); \ + } + CASE(NO_PAD) + CASE(CNT_CODE) + CASE(CNT_INITIALIZED_DATA) + CASE(CNT_UNINITIALIZED_DATA) + CASE(LNK_INFO) + CASE(LNK_REMOVE) + CASE(LNK_COMDAT) + CASE(GPREL) + CASE(LNK_NRELOC_OVFL) + CASE(MEM_DISCARDABLE) + CASE(MEM_NOT_CACHED) + CASE(MEM_NOT_PAGED) + CASE(MEM_SHARED) + CASE(MEM_EXECUTE) + CASE(MEM_READ) + CASE(MEM_WRITE) + printf("|\n"); +#undef CASE +} + + +void execformat_pe_ReadSystemHeader(execformat_pe_PortableExecutable *pe) { + execformat_pe_OptionalHeader_CheckPacking(); + + uint64_t a; + asm volatile("leaq (%%rip), %0" + : "=r"(a)); + io_Printf("Stack position: %llx, RIP=%llx\n", &a, a); + + void *addr = (void *)paging_LoaderCodeAddress; + + execformat_pe_PortableExecutable rpe; + if (pe == 0) + pe = &rpe; + execformat_pe_LoadMemory(pe, addr, paging_LoaderCodeSize); + + printf("%s Executable, Machine Type=0x%x (%s), Flags=", pe->isPE32P ? "PE32+" : "PE32", pe->header->machineType, caseMachine(pe->header->machineType)); + caseFlags(pe->header->flags); + printf("\nImageBase=%d (0x%x)\n", ((execformat_pe_OptionalHeader_PE32P *)pe->optional)->win.imageBase, ((execformat_pe_OptionalHeader_PE32P *)pe->optional)->win.imageBase); + + printf("numRVAandSizes=%d (want %d)\n\n", pe->numDataDir, EXECFORMAT_PE_DATADIR_INDEX_CLR_RUNTIME_HEADER + 2); + + printf("numSections=%d\n", pe->numSections); + char name[9]; + for (int i = 0; i < pe->numSections; i++) { + name[8] = 0; + strncpy(name, pe->sections[i].name, 8); + + printf(" %8s RVA0x%08x File0x%08x Len0x%08x ", name, pe->sections[i].virtualAddr, pe->sections[i].pointerToRawData, pe->sections[i].virtualSize); + + caseSectionFlags(pe->sections[i].flags); + } +} diff --git a/execformat/pe/reloc.c b/execformat/pe/reloc.c new file mode 100644 index 0000000..32d0297 --- /dev/null +++ b/execformat/pe/reloc.c @@ -0,0 +1,108 @@ + +#include "reloc.h" +#include "../../runtime/panic_assert.h" +#include + + +static inline int __local_strncmp(const char *s1, const char *s2, size_t n) { + while (n && *s1 && (*s1 == *s2)) { + ++s1; + ++s2; + --n; + } + if (n == 0) { + return 0; + } else { + return (*(unsigned char *)s1 - *(unsigned char *)s2); + } +} + +static const char *caseFlags(uint16_t type) { +#define CASE(expr) \ + case EXECFORMAT_PE_BASERELOC_##expr: \ + return #expr; + + switch (type) { + CASE(ABSOLUTE) + CASE(HIGH) + CASE(LOW) + CASE(HIGHLOW) + CASE(HIGHADJ) + CASE(DIR64) + default: + return "(Unknown)"; + } + +#undef CASE +} + +void execformat_pe_BaseRelocate(execformat_pe_PortableExecutable *pe, void *relocBase, void *relocEnd, uint64_t currentBase, uint64_t targetBase) { + if (pe->begin == 0) + return; + + if (currentBase == 0) { + if (pe->isPE32P) + currentBase = ((execformat_pe_OptionalHeader_PE32P *)pe->optional)->win.imageBase; + else + currentBase = ((execformat_pe_OptionalHeader_PE32 *)pe->optional)->win.imageBase; + } + + void *reloc = 0, *reloc_end; + if (relocBase != 0 && relocEnd != 0) { + reloc = relocBase; + reloc_end = relocEnd; + } else { + // find the .reloc section + for (int i = 0; i < pe->numSections; i++) + if (__local_strncmp(pe->sections[i].name, ".reloc", 6) == 0) { + reloc = pe->begin + pe->sections[i].virtualAddr; + reloc_end = reloc + pe->sections[i].virtualSize; + break; + } + if (reloc == 0) + return; // no .reloc section + } + + uint64_t diff = targetBase - currentBase; + + while (reloc < reloc_end) { + execformat_pe_BaseRelocBlock *block = reloc; + int entries = (block->blockSize - 8) / 2; + assert((block->blockSize - 8) % 2 == 0); + + printf("BaseReloc Block RVA=0x%08x, entries=%d\n", block->pageOffset, entries); + + for (int i = 0; i < entries; i++) { + void *target = pe->begin + block->pageOffset + (block->entries[i] & EXECFORMAT_PE_BASERELOC_OFFSET_MASK); + //uint64_t cur = 0, new = 0; + switch (block->entries[i] & EXECFORMAT_PE_BASERELOC_FLAG_MASK) { + case EXECFORMAT_PE_BASERELOC_HIGH: + //cur = *((uint16_t *)target); + *((uint16_t *)target) += (uint16_t)(diff >> 16); + //new = *((uint16_t *)target); + break; + case EXECFORMAT_PE_BASERELOC_LOW: + //cur = *((uint16_t *)target); + *((uint16_t *)target) += (uint16_t)(diff); + //new = *((uint16_t *)target); + break; + case EXECFORMAT_PE_BASERELOC_HIGHLOW: + //cur = *((uint32_t *)target); + *((uint32_t *)target) += (uint32_t)(diff); + //new = *((uint32_t *)target); + break; + case EXECFORMAT_PE_BASERELOC_DIR64: + //cur = *((uint64_t *)target); + *((uint64_t *)target) += (diff); + //new = *((uint64_t *)target); + break; + case EXECFORMAT_PE_BASERELOC_HIGHADJ: + i++; + break; + } + //printf(" Reloc Off0x%08llx CUR=0x%08llx(Off0x%08llx), NEW=0x%llx, Type=%s\n", target - pe->begin, cur, cur - currentBase, new, caseFlags(block->entries[i] & EXECFORMAT_PE_BASERELOC_FLAG_MASK)); + } + + reloc += block->blockSize; + } +} diff --git a/execformat/pe/reloc.h b/execformat/pe/reloc.h new file mode 100644 index 0000000..ed251e1 --- /dev/null +++ b/execformat/pe/reloc.h @@ -0,0 +1,30 @@ + +#include "format.h" +#include "struct.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct { + uint32_t pageOffset; + uint32_t blockSize; // the size in bytes of the Relocation Block, including the 2 header fields and the entries that follow + uint16_t entries[1]; +} PACKED execformat_pe_BaseRelocBlock; + +#define EXECFORMAT_PE_BASERELOC_ABSOLUTE (0x0000u) // The base relocation is skipped. This type can be used to pad a block. +#define EXECFORMAT_PE_BASERELOC_HIGH (0x1000u) // The base relocation adds the high 16 bits of the difference to the 16-bit field at offset. +#define EXECFORMAT_PE_BASERELOC_LOW (0x2000u) // The base relocation adds the low 16 bits of the difference to the 16-bit field at offset. +#define EXECFORMAT_PE_BASERELOC_HIGHLOW (0x3000u) // The base relocation applies all 32 bits of the difference to the 32-bit field at offset. +#define EXECFORMAT_PE_BASERELOC_HIGHADJ (0x4000u) // Weird, ignored (together with the next entry). +#define EXECFORMAT_PE_BASERELOC_DIR64 (0xa000u) // The base relocation applies the difference to the 64-bit field at offset. +#define EXECFORMAT_PE_BASERELOC_FLAG_MASK (0xf000u) +#define EXECFORMAT_PE_BASERELOC_OFFSET_MASK (0x0fffu) + +void execformat_pe_BaseRelocate(execformat_pe_PortableExecutable *pe, void *relocBase, void *relocEnd, uint64_t currentBase, uint64_t targetBase); + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/execformat/pe/struct.c b/execformat/pe/struct.c new file mode 100644 index 0000000..0067407 --- /dev/null +++ b/execformat/pe/struct.c @@ -0,0 +1,50 @@ + +#include "struct.h" + +#include "../../runtime/panic_assert.h" +#include + + +void execformat_pe_LoadMemory(execformat_pe_PortableExecutable *pe, void *image, uint32_t size) { + pe->begin = pe->pemagic = 0; + if (size < 2 || ((char *)image)[0] != 'M' || ((char *)image)[1] != 'Z') + return; + + // look for the "PE\0\0" magic on a 8-byte boundary + for (int i = 8; i < size; i++) { + if (memcmp(image + i, EXECFORMAT_PE_HEADER_MAGIC, 4) == 0) { + pe->pemagic = image + i; + break; + } + } + if (pe->pemagic == 0) + return; // not found + + pe->header = (execformat_pe_Header *)(pe->pemagic + 4); + pe->numSections = pe->header->numSections; + pe->optional = (void *)pe->header + sizeof(execformat_pe_Header); + pe->isPE32P = (*((uint16_t *)pe->optional) == EXECFORMAT_PE_OPTIONAL_HEADER_MAGIC_PE32P); + + if (pe->isPE32P) { + pe->numDataDir = ((execformat_pe_OptionalHeader_PE32P *)pe->optional)->win.numRVAandSizes; + pe->sections = + pe->optional + + sizeof(execformat_pe_OptionalHeader_StandardFields_PE32P) + + sizeof(execformat_pe_OptionalHeader_WindowsFields_PE32P) + + sizeof(execformat_pe_OptionalHeader_DataDirectory) * pe->numDataDir; + + assert((void *)pe->sections - pe->optional == pe->header->sizeofOptionalHeader && "PE32P OptionalHeader size mismatch"); + } else { + pe->numDataDir = ((execformat_pe_OptionalHeader_PE32 *)pe->optional)->win.numRVAandSizes; + pe->sections = + pe->optional + + sizeof(execformat_pe_OptionalHeader_StandardFields_PE32) + + sizeof(execformat_pe_OptionalHeader_WindowsFields_PE32) + + sizeof(execformat_pe_OptionalHeader_DataDirectory) * pe->numDataDir; + + assert((void *)pe->sections - pe->optional == pe->header->sizeofOptionalHeader && "PE32 OptionalHeader size mismatch"); + } + + pe->begin = image; + pe->size = size; +} diff --git a/execformat/pe/struct.h b/execformat/pe/struct.h new file mode 100644 index 0000000..3993a93 --- /dev/null +++ b/execformat/pe/struct.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../../main.h" +#include "format.h" +#include "stdbool.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct { + void * begin; // beginning of the image + char * pemagic; // PE magic "PE\0\0", after the MS-DOS stub + execformat_pe_Header * header; // PE headers, 4 bytes right after the magic + void * optional; // optional headers, converted to OptionalHeader_PE32/PE32P on access + execformat_pe_SectionHeader *sections; // start of the section header tables + uint32_t size; // size of the file in bytes + int numDataDir; // shorthand for ((execformat_pe_OptionalHeader_PE32P*)pe->optional)->win.numRVAandSizes + int numSections; + bool isPE32P; +} execformat_pe_PortableExecutable; + +void execformat_pe_LoadMemory(execformat_pe_PortableExecutable *pe, void *image, uint32_t size); + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/execformat/pe/test_headers.c b/execformat/pe/test_headers.c new file mode 100644 index 0000000..2a43c79 --- /dev/null +++ b/execformat/pe/test_headers.c @@ -0,0 +1,118 @@ + +#include "format.h" +#include "struct.h" +#include "reloc.h" + +#include "stdio.h" +#include "sys/types.h" +#include "sys/stat.h" +#include "fcntl.h" +#include "sys/mman.h" + +#include +#include +#include + +const char *caseMachine(uint16_t type) { +#define CASE(expr) \ + case EXECFORMAT_PE_##expr: \ + return #expr; + + switch (type) { + CASE(MACHINE_UNKNOWN) + CASE(MACHINE_AMD64) + CASE(MACHINE_I386) + CASE(MACHINE_EBC) + default: + return "(Unknown)"; + } + +#undef CASE +} + +void caseFlags(uint32_t flags) { +#define CASE(expr) \ + if (flags & EXECFORMAT_PE_FLAG_##expr) { \ + printf("|" #expr); \ + } + CASE(RELOCS_STRIPPED) + CASE(EXECUTABLE_IMAGE) + CASE(LINE_NUMS_STRIPPED) + CASE(LOCAL_SYMS_STRIPPED) + CASE(AGGRESSIVE_WS_TRIM) + CASE(LARGE_ADDRESS_AWARE) + CASE(RESERVED_0x0040) + CASE(BYTES_REVERSED_LO) + CASE(32BIT_MACHINE) + CASE(DEBUG_STRIPPED) + CASE(REMOVABLE_RUN_FROM_SWAP) + CASE(NET_RUN_FROM_SWAP) + CASE(SYSTEM) + CASE(DLL) + CASE(UP_SYSTEM_ONLY) + CASE(BYTES_REVERSED_HI) + printf("|\n"); +#undef CASE +} +void caseSectionFlags(uint32_t flags) { +#define CASE(expr) \ + if (flags & EXECFORMAT_PE_SECTIONFLAG_##expr) { \ + printf("|" #expr); \ + } + CASE(NO_PAD) + CASE(CNT_CODE) + CASE(CNT_INITIALIZED_DATA) + CASE(CNT_UNINITIALIZED_DATA) + CASE(LNK_INFO) + CASE(LNK_REMOVE) + CASE(LNK_COMDAT) + CASE(GPREL) + CASE(LNK_NRELOC_OVFL) + CASE(MEM_DISCARDABLE) + CASE(MEM_NOT_CACHED) + CASE(MEM_NOT_PAGED) + CASE(MEM_SHARED) + CASE(MEM_EXECUTE) + CASE(MEM_READ) + CASE(MEM_WRITE) + printf("|\n"); +#undef CASE +} + + +int main(int argc, char *argv[]) { + execformat_pe_OptionalHeader_CheckPacking(); + + int f = open("../../Main.efi", O_RDONLY); + assert(f != -1 && "File open failed"); + + struct stat st; + fstat(f, &st); + void *maddr = mmap(0, st.st_size, PROT_READ, MAP_SHARED, f, 0); + void *addr = malloc(st.st_size); + memcpy(addr, maddr, st.st_size); + + execformat_pe_PortableExecutable pe; + execformat_pe_LoadMemory(&pe, addr, st.st_size); + + printf("%s Executable, Machine Type=0x%x (%s), Flags=", pe.isPE32P ? "PE32+" : "PE32", pe.header->machineType, caseMachine(pe.header->machineType)); + caseFlags(pe.header->flags); + printf("\nImageBase=%d (0x%x)\n", ((execformat_pe_OptionalHeader_PE32P *)pe.optional)->win.imageBase, ((execformat_pe_OptionalHeader_PE32P *)pe.optional)->win.imageBase); + + printf("numRVAandSizes=%d (want %d)\n\n", pe.numDataDir, EXECFORMAT_PE_DATADIR_INDEX_CLR_RUNTIME_HEADER + 2); + + printf("numSections=%d\n", pe.numSections); + char name[9]; + for (int i = 0; i < pe.numSections; i++) { + name[8] = 0; + strncpy(name, pe.sections[i].name, 8); + + printf(" %8s Rva0x%08x File0x%08x Len0x%08x ", name, pe.sections[i].virtualAddr, pe.sections[i].pointerToRawData, pe.sections[i].virtualSize); + caseSectionFlags(pe.sections[i].flags); + } + + + execformat_pe_BaseRelocate(&pe, 0, 0xFFFFFFFFC0000000ull); + + return 0; +} diff --git a/extlib/Makefile.old b/extlib/Makefile.old new file mode 100644 index 0000000..45078df --- /dev/null +++ b/extlib/Makefile.old @@ -0,0 +1,20 @@ + +SELF_DIR = $(dir $@) + +export AR = echo " AR extlib/$(if $<,$<,$(SELF_DIR)$$<)" && x86_64-w64-mingw32-ar +export AS = echo " AS extlib/$(if $<,$<,$(SELF_DIR)$$<)" && x86_64-w64-mingw32-as +export CC = echo " CC extlib/$(if $<,$<,$(SELF_DIR)$$<)" && x86_64-w64-mingw32-gcc +export CXX = echo " CXX extlib/$(if $<,$<,$(SELF_DIR)$$<)" && x86_64-w64-mingw32-g++ +export LD = echo " LD extlib/$@" && x86_64-w64-mingw32-gcc +export FASM = echo " FASM extlib/$(SELF_DIR)$$(patsubst %.o,%.S,$$@)" && fasm >/dev/null + +SUBDIRS = $(wildcard */.) +$(SUBDIRS): + echo $@ + echo $(SUBDIRS) + $(MAKE) -sC $@ + +all: $(SUBDIRS) + +.PHONY: all $(SUBDIRS) + diff --git a/extlib/liballoc/HOWTO b/extlib/liballoc/HOWTO new file mode 100644 index 0000000..fb35cd3 --- /dev/null +++ b/extlib/liballoc/HOWTO @@ -0,0 +1,26 @@ + +This is a step-by-step instruction guide detailing how to +implement the library on your own system. + + +1. Copy the "liballoc.c" and "liballoc.h" files into + your working directory. (Whatever project you want + to use it in). + +2. Create the hooks that the library needs: + + Make a new file called "liballoc_hooks.c" (or + whatever) and implement the functions detailed + in the README and explained in "liballoc.h" + + Look at "linux.c" for an example. It implements + the hooks for a Linux system. + +3. Be sure to include the "liballoc.h" header + into your C/C++ files that are using the + malloc/free/memory operations. (So that + malloc/free/etc are declared.. obviously.) + +4. Compile as per your normal method and test. + + diff --git a/extlib/liballoc/LICENSE b/extlib/liballoc/LICENSE new file mode 100644 index 0000000..63d14cd --- /dev/null +++ b/extlib/liballoc/LICENSE @@ -0,0 +1,10 @@ + +This code is released into the public domain. Use this code at your own +risk. Feel free to use it for whatever purpose you want. I take no responsibilty or +whatever if anything goes wrong. Use it at your own risk. + +If you have any fixes or patches, please email me. + +Durand Miller + + diff --git a/extlib/liballoc/README.md b/extlib/liballoc/README.md new file mode 100644 index 0000000..d17dfc1 --- /dev/null +++ b/extlib/liballoc/README.md @@ -0,0 +1,68 @@ +https://github.com/blanham/liballoc + +liballoc - a small memory allocator +=================================== + +This is liballoc, a memory allocator for hobby operating systems, originally +written by Durand. According to the original page for liballoc it was released +into the public domain, but the copy I have contains the 3 clause BSD license. + +liballoc.c/h are the original release of liballoc taken from the spoon tarball +while liballoc_1_1.c/h are later versions found by detective work using Google. + +Using liballoc +============== + +There are 4 functions which you need to implement on your system: + + int liballoc_lock(); + int liballoc_unlock(); + void* liballoc_alloc(int); + int liballoc_free(void*,int); + +1) Have a look at liballoc.h for information about what each function is +supposed to do. + + +2) Have a look at linux.c for an example of how to implement the library +on linux. + + +NOTE: There are two ways to build the library: + + 1) Compile the library with a new system file. For example, I've + left linux.c with the default distribution. It gets compiled + directly into the liballoc_linux.so file. + + 2) Implement the functions in your application and then just + link against the default liballoc.so library when you compile + your app. + + +Quick Start +=========== + +You can simply type: "make linux" to build the linux shared +library. Thereafter, you can link it directly into your applications +during build or afterwards by export the LD_PRELOAD environment +variable. + + +To run bash with the library, for example: + + LD_PRELOAD=/full/path/to/liballoc.so bash + + +The above command will pre-link the library into the application, +essentially replacing the default malloc/free calls at runtime. It's +quite cool. + + +Originally by: +Durand Miller + + + + + + diff --git a/extlib/liballoc/liballoc_1_1.c b/extlib/liballoc/liballoc_1_1.c new file mode 100644 index 0000000..1c82d8f --- /dev/null +++ b/extlib/liballoc/liballoc_1_1.c @@ -0,0 +1,832 @@ +#include "liballoc_1_1.h" + +/** Durand's Amazing Super Duper Memory functions. */ + +#define VERSION "1.1" +#define ALIGNMENT 16ul//4ul ///< This is the byte alignment that memory must be allocated on. IMPORTANT for GTK and other stuff. + +#define ALIGN_TYPE char ///unsigned char[16] /// unsigned short +#define ALIGN_INFO sizeof(ALIGN_TYPE)*16 ///< Alignment information is stored right before the pointer. This is the number of bytes of information stored there. + + +#define USE_CASE1 +#define USE_CASE2 +#define USE_CASE3 +#define USE_CASE4 +#define USE_CASE5 + + +/** This macro will conveniently align our pointer upwards */ +#define ALIGN( ptr ) \ + if ( ALIGNMENT > 1 ) \ + { \ + uintptr_t diff; \ + ptr = (void*)((uintptr_t)ptr + ALIGN_INFO); \ + diff = (uintptr_t)ptr & (ALIGNMENT-1); \ + if ( diff != 0 ) \ + { \ + diff = ALIGNMENT - diff; \ + ptr = (void*)((uintptr_t)ptr + diff); \ + } \ + *((ALIGN_TYPE*)((uintptr_t)ptr - ALIGN_INFO)) = \ + diff + ALIGN_INFO; \ + } + + +#define UNALIGN( ptr ) \ + if ( ALIGNMENT > 1 ) \ + { \ + uintptr_t diff = *((ALIGN_TYPE*)((uintptr_t)ptr - ALIGN_INFO)); \ + if ( diff < (ALIGNMENT + ALIGN_INFO) ) \ + { \ + ptr = (void*)((uintptr_t)ptr - diff); \ + } \ + } + + + +#define LIBALLOC_MAGIC 0xc001c0de +#define LIBALLOC_DEAD 0xdeaddead + +#if defined DEBUG || defined INFO +#include +#include + +#define FLUSH() fflush( stdout ) + +#endif + +/** A structure found at the top of all system allocated + * memory blocks. It details the usage of the memory block. + */ +struct liballoc_major +{ + struct liballoc_major *prev; ///< Linked list information. + struct liballoc_major *next; ///< Linked list information. + unsigned int pages; ///< The number of pages in the block. + unsigned int size; ///< The number of pages in the block. + unsigned int usage; ///< The number of bytes used in the block. + struct liballoc_minor *first; ///< A pointer to the first allocated memory in the block. +}; + + +/** This is a structure found at the beginning of all + * sections in a major block which were allocated by a + * malloc, calloc, realloc call. + */ +struct liballoc_minor +{ + struct liballoc_minor *prev; ///< Linked list information. + struct liballoc_minor *next; ///< Linked list information. + struct liballoc_major *block; ///< The owning block. A pointer to the major structure. + unsigned int magic; ///< A magic number to idenfity correctness. + unsigned int size; ///< The size of the memory allocated. Could be 1 byte or more. + unsigned int req_size; ///< The size of memory requested. +}; + + +static struct liballoc_major *l_memRoot = NULL; ///< The root memory block acquired from the system. +static struct liballoc_major *l_bestBet = NULL; ///< The major with the most free memory. + +static unsigned int l_pageSize = 4096; ///< The size of an individual page. Set up in liballoc_init. +static unsigned int l_pageCount = 16; ///< The number of pages to request per chunk. Set up in liballoc_init. +static unsigned long long l_allocated = 0; ///< Running total of allocated memory. +static unsigned long long l_inuse = 0; ///< Running total of used memory. + + +static long long l_warningCount = 0; ///< Number of warnings encountered +static long long l_errorCount = 0; ///< Number of actual errors +static long long l_possibleOverruns = 0; ///< Number of possible overruns + + + + + +// *********** HELPER FUNCTIONS ******************************* + +static void *liballoc_memset(void* s, int c, size_t n) +{ + unsigned int i; + for ( i = 0; i < n ; i++) + ((char*)s)[i] = c; + + return s; +} +static void* liballoc_memcpy(void* s1, const void* s2, size_t n) +{ + char *cdest; + char *csrc; + unsigned int *ldest = (unsigned int*)s1; + unsigned int *lsrc = (unsigned int*)s2; + + while ( n >= sizeof(unsigned int) ) + { + *ldest++ = *lsrc++; + n -= sizeof(unsigned int); + } + + cdest = (char*)ldest; + csrc = (char*)lsrc; + + while ( n > 0 ) + { + *cdest++ = *csrc++; + n -= 1; + } + + return s1; +} + + +#if defined DEBUG || defined INFO +static void liballoc_dump() +{ +#ifdef DEBUG + struct liballoc_major *maj = l_memRoot; + struct liballoc_minor *min = NULL; +#endif + + printf( "liballoc: ------ Memory data ---------------\n"); + printf( "liballoc: System memory allocated: %i bytes\n", l_allocated ); + printf( "liballoc: Memory in used (malloc'ed): %i bytes\n", l_inuse ); + printf( "liballoc: Warning count: %i\n", l_warningCount ); + printf( "liballoc: Error count: %i\n", l_errorCount ); + printf( "liballoc: Possible overruns: %i\n", l_possibleOverruns ); + +#ifdef DEBUG + while ( maj != NULL ) + { + printf( "liballoc: %x: total = %i, used = %i\n", + maj, + maj->size, + maj->usage ); + + min = maj->first; + while ( min != NULL ) + { + printf( "liballoc: %x: %i bytes\n", + min, + min->size ); + min = min->next; + } + + maj = maj->next; + } +#endif + + FLUSH(); +} +#endif + + + +// *************************************************************** + +static struct liballoc_major *allocate_new_page( unsigned int size ) +{ + unsigned int st; + struct liballoc_major *maj; + + // This is how much space is required. + st = size + sizeof(struct liballoc_major); + st += sizeof(struct liballoc_minor); + + // Perfect amount of space? + if ( (st % l_pageSize) == 0 ) + st = st / (l_pageSize); + else + st = st / (l_pageSize) + 1; + // No, add the buffer. + + + // Make sure it's >= the minimum size. + if ( st < l_pageCount ) st = l_pageCount; + + maj = (struct liballoc_major*)liballoc_alloc( st ); + + if ( maj == NULL ) + { + l_warningCount += 1; + #if defined DEBUG || defined INFO + printf( "liballoc: WARNING: liballoc_alloc( %i ) return NULL\n", st ); + FLUSH(); + #endif + return NULL; // uh oh, we ran out of memory. + } + + maj->prev = NULL; + maj->next = NULL; + maj->pages = st; + maj->size = st * l_pageSize; + maj->usage = sizeof(struct liballoc_major); + maj->first = NULL; + + l_allocated += maj->size; + + #ifdef DEBUG + printf( "liballoc: Resource allocated %x of %i pages (%i bytes) for %i size.\n", maj, st, maj->size, size ); + + printf( "liballoc: Total memory usage = %i KB\n", (int)((l_allocated / (1024))) ); + FLUSH(); + #endif + + + return maj; +} + + + + + +void *PREFIX(malloc)(size_t req_size) +{ + int startedBet = 0; + unsigned long long bestSize = 0; + void *p = NULL; + uintptr_t diff; + struct liballoc_major *maj; + struct liballoc_minor *min; + struct liballoc_minor *new_min; + unsigned long size = req_size; + + // For alignment, we adjust size so there's enough space to align. + if ( ALIGNMENT > 1 ) + { + size += ALIGNMENT + ALIGN_INFO; + } + // So, ideally, we really want an alignment of 0 or 1 in order + // to save space. + + liballoc_lock(); + + if ( size == 0 ) + { + l_warningCount += 1; + #if defined DEBUG || defined INFO + printf( "liballoc: WARNING: alloc( 0 ) called from %x\n", + __builtin_return_address(0) ); + FLUSH(); + #endif + liballoc_unlock(); + return PREFIX(malloc)(1); + } + + + if ( l_memRoot == NULL ) + { + #if defined DEBUG || defined INFO + #ifdef DEBUG + printf( "liballoc: initialization of liballoc " VERSION "\n" ); + #endif + atexit( liballoc_dump ); + FLUSH(); + #endif + + // This is the first time we are being used. + l_memRoot = allocate_new_page( size ); + if ( l_memRoot == NULL ) + { + liballoc_unlock(); + #ifdef DEBUG + printf( "liballoc: initial l_memRoot initialization failed\n", p); + FLUSH(); + #endif + return NULL; + } + + #ifdef DEBUG + printf( "liballoc: set up first memory major %x\n", l_memRoot ); + FLUSH(); + #endif + } + + + #ifdef DEBUG + printf( "liballoc: %x PREFIX(malloc)( %i ): ", + __builtin_return_address(0), + size ); + FLUSH(); + #endif + + // Now we need to bounce through every major and find enough space.... + + maj = l_memRoot; + startedBet = 0; + + // Start at the best bet.... + if ( l_bestBet != NULL ) + { + bestSize = l_bestBet->size - l_bestBet->usage; + + if ( bestSize > (size + sizeof(struct liballoc_minor))) + { + maj = l_bestBet; + startedBet = 1; + } + } + + while ( maj != NULL ) + { + diff = maj->size - maj->usage; + // free memory in the block + + if ( bestSize < diff ) + { + // Hmm.. this one has more memory then our bestBet. Remember! + l_bestBet = maj; + bestSize = diff; + } + + +#ifdef USE_CASE1 + + // CASE 1: There is not enough space in this major block. + if ( diff < (size + sizeof( struct liballoc_minor )) ) + { + #ifdef DEBUG + printf( "CASE 1: Insufficient space in block %x\n", maj); + FLUSH(); + #endif + + // Another major block next to this one? + if ( maj->next != NULL ) + { + maj = maj->next; // Hop to that one. + continue; + } + + if ( startedBet == 1 ) // If we started at the best bet, + { // let's start all over again. + maj = l_memRoot; + startedBet = 0; + continue; + } + + // Create a new major block next to this one and... + maj->next = allocate_new_page( size ); // next one will be okay. + if ( maj->next == NULL ) break; // no more memory. + maj->next->prev = maj; + maj = maj->next; + + // .. fall through to CASE 2 .. + } + +#endif + +#ifdef USE_CASE2 + + // CASE 2: It's a brand new block. + if ( maj->first == NULL ) + { + maj->first = (struct liballoc_minor*)((uintptr_t)maj + sizeof(struct liballoc_major) ); + + + maj->first->magic = LIBALLOC_MAGIC; + maj->first->prev = NULL; + maj->first->next = NULL; + maj->first->block = maj; + maj->first->size = size; + maj->first->req_size = req_size; + maj->usage += size + sizeof( struct liballoc_minor ); + + + l_inuse += size; + + + p = (void*)((uintptr_t)(maj->first) + sizeof( struct liballoc_minor )); + + ALIGN( p ); + + #ifdef DEBUG + printf( "CASE 2: returning %x\n", p); + FLUSH(); + #endif + liballoc_unlock(); // release the lock + return p; + } + +#endif + +#ifdef USE_CASE3 + + // CASE 3: Block in use and enough space at the start of the block. + diff = (uintptr_t)(maj->first); + diff -= (uintptr_t)maj; + diff -= sizeof(struct liballoc_major); + + if ( diff >= (size + sizeof(struct liballoc_minor)) ) + { + // Yes, space in front. Squeeze in. + maj->first->prev = (struct liballoc_minor*)((uintptr_t)maj + sizeof(struct liballoc_major) ); + maj->first->prev->next = maj->first; + maj->first = maj->first->prev; + + maj->first->magic = LIBALLOC_MAGIC; + maj->first->prev = NULL; + maj->first->block = maj; + maj->first->size = size; + maj->first->req_size = req_size; + maj->usage += size + sizeof( struct liballoc_minor ); + + l_inuse += size; + + p = (void*)((uintptr_t)(maj->first) + sizeof( struct liballoc_minor )); + ALIGN( p ); + + #ifdef DEBUG + printf( "CASE 3: returning %x\n", p); + FLUSH(); + #endif + liballoc_unlock(); // release the lock + return p; + } + +#endif + + +#ifdef USE_CASE4 + + // CASE 4: There is enough space in this block. But is it contiguous? + min = maj->first; + + // Looping within the block now... + while ( min != NULL ) + { + // CASE 4.1: End of minors in a block. Space from last and end? + if ( min->next == NULL ) + { + // the rest of this block is free... is it big enough? + diff = (uintptr_t)(maj) + maj->size; + diff -= (uintptr_t)min; + diff -= sizeof( struct liballoc_minor ); + diff -= min->size; + // minus already existing usage.. + + if ( diff >= (size + sizeof( struct liballoc_minor )) ) + { + // yay.... + min->next = (struct liballoc_minor*)((uintptr_t)min + sizeof( struct liballoc_minor ) + min->size); + min->next->prev = min; + min = min->next; + min->next = NULL; + min->magic = LIBALLOC_MAGIC; + min->block = maj; + min->size = size; + min->req_size = req_size; + maj->usage += size + sizeof( struct liballoc_minor ); + + l_inuse += size; + + p = (void*)((uintptr_t)min + sizeof( struct liballoc_minor )); + ALIGN( p ); + + #ifdef DEBUG + printf( "CASE 4.1: returning %x\n", p); + FLUSH(); + #endif + liballoc_unlock(); // release the lock + return p; + } + } + + + + // CASE 4.2: Is there space between two minors? + if ( min->next != NULL ) + { + // is the difference between here and next big enough? + diff = (uintptr_t)(min->next); + diff -= (uintptr_t)min; + diff -= sizeof( struct liballoc_minor ); + diff -= min->size; + // minus our existing usage. + + if ( diff >= (size + sizeof( struct liballoc_minor )) ) + { + // yay...... + new_min = (struct liballoc_minor*)((uintptr_t)min + sizeof( struct liballoc_minor ) + min->size); + + new_min->magic = LIBALLOC_MAGIC; + new_min->next = min->next; + new_min->prev = min; + new_min->size = size; + new_min->req_size = req_size; + new_min->block = maj; + min->next->prev = new_min; + min->next = new_min; + maj->usage += size + sizeof( struct liballoc_minor ); + + l_inuse += size; + + p = (void*)((uintptr_t)new_min + sizeof( struct liballoc_minor )); + ALIGN( p ); + + + #ifdef DEBUG + printf( "CASE 4.2: returning %x\n", p); + FLUSH(); + #endif + + liballoc_unlock(); // release the lock + return p; + } + } // min->next != NULL + + min = min->next; + } // while min != NULL ... + + +#endif + +#ifdef USE_CASE5 + + // CASE 5: Block full! Ensure next block and loop. + if ( maj->next == NULL ) + { + #ifdef DEBUG + printf( "CASE 5: block full\n"); + FLUSH(); + #endif + + if ( startedBet == 1 ) + { + maj = l_memRoot; + startedBet = 0; + continue; + } + + // we've run out. we need more... + maj->next = allocate_new_page( size ); // next one guaranteed to be okay + if ( maj->next == NULL ) break; // uh oh, no more memory..... + maj->next->prev = maj; + + } + +#endif + + maj = maj->next; + } // while (maj != NULL) + + + + liballoc_unlock(); // release the lock + + #ifdef DEBUG + printf( "All cases exhausted. No memory available.\n"); + FLUSH(); + #endif + #if defined DEBUG || defined INFO + printf( "liballoc: WARNING: PREFIX(malloc)( %i ) returning NULL.\n", size); + liballoc_dump(); + FLUSH(); + #endif + return NULL; +} + + + + + + + + + +void PREFIX(free)(void *ptr) +{ + struct liballoc_minor *min; + struct liballoc_major *maj; + + if ( ptr == NULL ) + { + l_warningCount += 1; + #if defined DEBUG || defined INFO + printf( "liballoc: WARNING: PREFIX(free)( NULL ) called from %x\n", + __builtin_return_address(0) ); + FLUSH(); + #endif + return; + } + + UNALIGN( ptr ); + + liballoc_lock(); // lockit + + + min = (struct liballoc_minor*)((uintptr_t)ptr - sizeof( struct liballoc_minor )); + + + if ( min->magic != LIBALLOC_MAGIC ) + { + l_errorCount += 1; + + // Check for overrun errors. For all bytes of LIBALLOC_MAGIC + if ( + ((min->magic & 0xFFFFFF) == (LIBALLOC_MAGIC & 0xFFFFFF)) || + ((min->magic & 0xFFFF) == (LIBALLOC_MAGIC & 0xFFFF)) || + ((min->magic & 0xFF) == (LIBALLOC_MAGIC & 0xFF)) + ) + { + l_possibleOverruns += 1; + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: Possible 1-3 byte overrun for magic %x != %x\n", + min->magic, + LIBALLOC_MAGIC ); + FLUSH(); + #endif + } + + + if ( min->magic == LIBALLOC_DEAD ) + { + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: multiple PREFIX(free)() attempt on %x from %x.\n", + ptr, + __builtin_return_address(0) ); + FLUSH(); + #endif + } + else + { + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: Bad PREFIX(free)( %x ) called from %x\n", + ptr, + __builtin_return_address(0) ); + FLUSH(); + #endif + } + + // being lied to... + liballoc_unlock(); // release the lock + return; + } + + #ifdef DEBUG + printf( "liballoc: %x PREFIX(free)( %x ): ", + __builtin_return_address( 0 ), + ptr ); + FLUSH(); + #endif + + + maj = min->block; + + l_inuse -= min->size; + + maj->usage -= (min->size + sizeof( struct liballoc_minor )); + min->magic = LIBALLOC_DEAD; // No mojo. + + if ( min->next != NULL ) min->next->prev = min->prev; + if ( min->prev != NULL ) min->prev->next = min->next; + + if ( min->prev == NULL ) maj->first = min->next; + // Might empty the block. This was the first + // minor. + + + // We need to clean up after the majors now.... + + if ( maj->first == NULL ) // Block completely unused. + { + if ( l_memRoot == maj ) l_memRoot = maj->next; + if ( l_bestBet == maj ) l_bestBet = NULL; + if ( maj->prev != NULL ) maj->prev->next = maj->next; + if ( maj->next != NULL ) maj->next->prev = maj->prev; + l_allocated -= maj->size; + + liballoc_free( maj, maj->pages ); + } + else + { + if ( l_bestBet != NULL ) + { + int bestSize = l_bestBet->size - l_bestBet->usage; + int majSize = maj->size - maj->usage; + + if ( majSize > bestSize ) l_bestBet = maj; + } + + } + + + #ifdef DEBUG + printf( "OK\n"); + FLUSH(); + #endif + + liballoc_unlock(); // release the lock +} + + + + + +void* PREFIX(calloc)(size_t nobj, size_t size) +{ + int real_size; + void *p; + + real_size = nobj * size; + + p = PREFIX(malloc)( real_size ); + + liballoc_memset( p, 0, real_size ); + + return p; +} + + + +void* PREFIX(realloc)(void *p, size_t size) +{ + void *ptr; + struct liballoc_minor *min; + unsigned int real_size; + + // Honour the case of size == 0 => free old and return NULL + if ( size == 0 ) + { + PREFIX(free)( p ); + return NULL; + } + + // In the case of a NULL pointer, return a simple malloc. + if ( p == NULL ) return PREFIX(malloc)( size ); + + // Unalign the pointer if required. + ptr = p; + UNALIGN(ptr); + + liballoc_lock(); // lockit + + min = (struct liballoc_minor*)((uintptr_t)ptr - sizeof( struct liballoc_minor )); + + // Ensure it is a valid structure. + if ( min->magic != LIBALLOC_MAGIC ) + { + l_errorCount += 1; + + // Check for overrun errors. For all bytes of LIBALLOC_MAGIC + if ( + ((min->magic & 0xFFFFFF) == (LIBALLOC_MAGIC & 0xFFFFFF)) || + ((min->magic & 0xFFFF) == (LIBALLOC_MAGIC & 0xFFFF)) || + ((min->magic & 0xFF) == (LIBALLOC_MAGIC & 0xFF)) + ) + { + l_possibleOverruns += 1; + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: Possible 1-3 byte overrun for magic %x != %x\n", + min->magic, + LIBALLOC_MAGIC ); + FLUSH(); + #endif + } + + + if ( min->magic == LIBALLOC_DEAD ) + { + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: multiple PREFIX(free)() attempt on %x from %x.\n", + ptr, + __builtin_return_address(0) ); + FLUSH(); + #endif + } + else + { + #if defined DEBUG || defined INFO + printf( "liballoc: ERROR: Bad PREFIX(free)( %x ) called from %x\n", + ptr, + __builtin_return_address(0) ); + FLUSH(); + #endif + } + + // being lied to... + liballoc_unlock(); // release the lock + return NULL; + } + + // Definitely a memory block. + + real_size = min->req_size; + + if ( real_size >= size ) + { + min->req_size = size; + liballoc_unlock(); + return p; + } + + liballoc_unlock(); + + // If we got here then we're reallocating to a block bigger than us. + ptr = PREFIX(malloc)( size ); // We need to allocate new memory + liballoc_memcpy( ptr, p, real_size ); + PREFIX(free)( p ); + + return ptr; +} + + + + diff --git a/extlib/liballoc/liballoc_1_1.h b/extlib/liballoc/liballoc_1_1.h new file mode 100644 index 0000000..e05c796 --- /dev/null +++ b/extlib/liballoc/liballoc_1_1.h @@ -0,0 +1,87 @@ +#ifndef _LIBALLOC_H +#define _LIBALLOC_H + +/** \defgroup ALLOCHOOKS liballoc hooks + * + * These are the OS specific functions which need to + * be implemented on any platform that the library + * is expected to work on. + */ + +/** @{ */ + +#include +#include + + +// If we are told to not define our own size_t, then we skip the define. +//#define _HAVE_UINTPTR_T +//typedef unsigned long uintptr_t; +typedef uint64_t uintptr_t; + +//This lets you prefix malloc and friends +#ifndef PREFIX +#define PREFIX(func) liballoc_k ## func +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + + +/** This function is supposed to lock the memory data structures. It + * could be as simple as disabling interrupts or acquiring a spinlock. + * It's up to you to decide. + * + * \return 0 if the lock was acquired successfully. Anything else is + * failure. + */ +extern int liballoc_lock(); + +/** This function unlocks what was previously locked by the liballoc_lock + * function. If it disabled interrupts, it enables interrupts. If it + * had acquiried a spinlock, it releases the spinlock. etc. + * + * \return 0 if the lock was successfully released. + */ +extern int liballoc_unlock(); + +/** This is the hook into the local system which allocates pages. It + * accepts an integer parameter which is the number of pages + * required. The page size was set up in the liballoc_init function. + * + * \return NULL if the pages were not allocated. + * \return A pointer to the allocated memory. + */ +extern void* liballoc_alloc(size_t); + +/** This frees previously allocated memory. The void* parameter passed + * to the function is the exact same value returned from a previous + * liballoc_alloc call. + * + * The integer value is the number of pages to free. + * + * \return 0 if the memory was successfully freed. + */ +extern int liballoc_free(void*,size_t); + + + + +extern void *PREFIX(malloc)(size_t); ///< The standard function. +extern void *PREFIX(realloc)(void *, size_t); ///< The standard function. +extern void *PREFIX(calloc)(size_t, size_t); ///< The standard function. +extern void PREFIX(free)(void *); ///< The standard function. + + +#ifdef __cplusplus +} +#endif + + +/** @} */ + +#endif + + diff --git a/extlib/libvterm/LICENSE b/extlib/libvterm/LICENSE new file mode 100644 index 0000000..0d05163 --- /dev/null +++ b/extlib/libvterm/LICENSE @@ -0,0 +1,23 @@ + + +The MIT License + +Copyright (c) 2008 Paul Evans + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/extlib/libvterm/encoding.c b/extlib/libvterm/encoding.c new file mode 100644 index 0000000..b329634 --- /dev/null +++ b/extlib/libvterm/encoding.c @@ -0,0 +1,223 @@ +#include "vterm_internal.h" + +#define UNICODE_INVALID 0xFFFD + +#if defined(DEBUG) && DEBUG > 1 +#define DEBUG_PRINT_UTF8 +#endif + +struct UTF8DecoderData { + // number of bytes remaining in this codepoint + int bytes_remaining; + + // number of bytes total in this codepoint once it's finished + // (for detecting overlongs) + int bytes_total; + + int this_cp; +}; + +static void init_utf8(VTermEncoding *enc, void *data_) { + struct UTF8DecoderData *data = data_; + + data->bytes_remaining = 0; + data->bytes_total = 0; +} + +static void decode_utf8(VTermEncoding *enc, void *data_, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) { + struct UTF8DecoderData *data = data_; + +#ifdef DEBUG_PRINT_UTF8 + printf("BEGIN UTF-8\n"); +#endif + + for (; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos]; + +#ifdef DEBUG_PRINT_UTF8 + printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining); +#endif + + if (c < 0x20) // C0 + return; + + else if (c >= 0x20 && c < 0x7f) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + cp[(*cpi)++] = c; +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 char: U+%04x\n", c); +#endif + data->bytes_remaining = 0; + } + + else if (c == 0x7f) // DEL + return; + + else if (c >= 0x80 && c < 0xc0) { + if (!data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + continue; + } + + data->this_cp <<= 6; + data->this_cp |= c & 0x3f; + data->bytes_remaining--; + + if (!data->bytes_remaining) { +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total); +#endif + // Check for overlong sequences + switch (data->bytes_total) { + case 2: + if (data->this_cp < 0x0080) + data->this_cp = UNICODE_INVALID; + break; + case 3: + if (data->this_cp < 0x0800) + data->this_cp = UNICODE_INVALID; + break; + case 4: + if (data->this_cp < 0x10000) + data->this_cp = UNICODE_INVALID; + break; + case 5: + if (data->this_cp < 0x200000) + data->this_cp = UNICODE_INVALID; + break; + case 6: + if (data->this_cp < 0x4000000) + data->this_cp = UNICODE_INVALID; + break; + } + // Now look for plain invalid ones + if ((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) || + data->this_cp == 0xFFFE || + data->this_cp == 0xFFFF) + data->this_cp = UNICODE_INVALID; +#ifdef DEBUG_PRINT_UTF8 + printf(" char: U+%04x\n", data->this_cp); +#endif + cp[(*cpi)++] = data->this_cp; + } + } + + else if (c >= 0xc0 && c < 0xe0) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x1f; + data->bytes_total = 2; + data->bytes_remaining = 1; + } + + else if (c >= 0xe0 && c < 0xf0) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x0f; + data->bytes_total = 3; + data->bytes_remaining = 2; + } + + else if (c >= 0xf0 && c < 0xf8) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x07; + data->bytes_total = 4; + data->bytes_remaining = 3; + } + + else if (c >= 0xf8 && c < 0xfc) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x03; + data->bytes_total = 5; + data->bytes_remaining = 4; + } + + else if (c >= 0xfc && c < 0xfe) { + if (data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x01; + data->bytes_total = 6; + data->bytes_remaining = 5; + } + + else { + cp[(*cpi)++] = UNICODE_INVALID; + } + } +} + +static VTermEncoding encoding_utf8 = { + .init = &init_utf8, + .decode = &decode_utf8, +}; + +static void decode_usascii(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) { + int is_gr = bytes[*pos] & 0x80; + + for (; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos] ^ is_gr; + + if (c < 0x20 || c == 0x7f || c >= 0x80) + return; + + cp[(*cpi)++] = c; + } +} + +static VTermEncoding encoding_usascii = { + .decode = &decode_usascii, +}; + +struct StaticTableEncoding { + const VTermEncoding enc; + const uint32_t chars[128]; +}; + +static void decode_table(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, const char bytes[], size_t *pos, size_t bytelen) { + struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc; + int is_gr = bytes[*pos] & 0x80; + + for (; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos] ^ is_gr; + + if (c < 0x20 || c == 0x7f || c >= 0x80) + return; + + if (table->chars[c]) + cp[(*cpi)++] = table->chars[c]; + else + cp[(*cpi)++] = c; + } +} + +#include "encoding/DECdrawing.inc" +#include "encoding/uk.inc" + +static struct { + VTermEncodingType type; + char designation; + VTermEncoding * enc; +} encodings[] = { + {ENC_UTF8, 'u', &encoding_utf8}, + {ENC_SINGLE_94, '0', (VTermEncoding *)&encoding_DECdrawing}, + {ENC_SINGLE_94, 'A', (VTermEncoding *)&encoding_uk}, + {ENC_SINGLE_94, 'B', &encoding_usascii}, + {0}, +}; + +/* This ought to be INTERNAL but isn't because it's used by unit testing */ +VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation) { + for (int i = 0; encodings[i].designation; i++) + if (encodings[i].type == type && encodings[i].designation == designation) + return encodings[i].enc; + return NULL; +} diff --git a/extlib/libvterm/encoding/DECdrawing.inc b/extlib/libvterm/encoding/DECdrawing.inc new file mode 100644 index 0000000..47093ed --- /dev/null +++ b/extlib/libvterm/encoding/DECdrawing.inc @@ -0,0 +1,36 @@ +static const struct StaticTableEncoding encoding_DECdrawing = { + { .decode = &decode_table }, + { + [0x60] = 0x25C6, + [0x61] = 0x2592, + [0x62] = 0x2409, + [0x63] = 0x240C, + [0x64] = 0x240D, + [0x65] = 0x240A, + [0x66] = 0x00B0, + [0x67] = 0x00B1, + [0x68] = 0x2424, + [0x69] = 0x240B, + [0x6a] = 0x2518, + [0x6b] = 0x2510, + [0x6c] = 0x250C, + [0x6d] = 0x2514, + [0x6e] = 0x253C, + [0x6f] = 0x23BA, + [0x70] = 0x23BB, + [0x71] = 0x2500, + [0x72] = 0x23BC, + [0x73] = 0x23BD, + [0x74] = 0x251C, + [0x75] = 0x2524, + [0x76] = 0x2534, + [0x77] = 0x252C, + [0x78] = 0x2502, + [0x79] = 0x2A7D, + [0x7a] = 0x2A7E, + [0x7b] = 0x03C0, + [0x7c] = 0x2260, + [0x7d] = 0x00A3, + [0x7e] = 0x00B7, + } +}; diff --git a/extlib/libvterm/encoding/uk.inc b/extlib/libvterm/encoding/uk.inc new file mode 100644 index 0000000..da1445d --- /dev/null +++ b/extlib/libvterm/encoding/uk.inc @@ -0,0 +1,6 @@ +static const struct StaticTableEncoding encoding_uk = { + { .decode = &decode_table }, + { + [0x23] = 0x00a3, + } +}; diff --git a/extlib/libvterm/fullwidth.inc b/extlib/libvterm/fullwidth.inc new file mode 100644 index 0000000..7ff142f --- /dev/null +++ b/extlib/libvterm/fullwidth.inc @@ -0,0 +1,104 @@ + { 0x1100, 0x115f }, + { 0x231a, 0x231b }, + { 0x2329, 0x232a }, + { 0x23e9, 0x23ec }, + { 0x23f0, 0x23f0 }, + { 0x23f3, 0x23f3 }, + { 0x25fd, 0x25fe }, + { 0x2614, 0x2615 }, + { 0x2648, 0x2653 }, + { 0x267f, 0x267f }, + { 0x2693, 0x2693 }, + { 0x26a1, 0x26a1 }, + { 0x26aa, 0x26ab }, + { 0x26bd, 0x26be }, + { 0x26c4, 0x26c5 }, + { 0x26ce, 0x26ce }, + { 0x26d4, 0x26d4 }, + { 0x26ea, 0x26ea }, + { 0x26f2, 0x26f3 }, + { 0x26f5, 0x26f5 }, + { 0x26fa, 0x26fa }, + { 0x26fd, 0x26fd }, + { 0x2705, 0x2705 }, + { 0x270a, 0x270b }, + { 0x2728, 0x2728 }, + { 0x274c, 0x274c }, + { 0x274e, 0x274e }, + { 0x2753, 0x2755 }, + { 0x2757, 0x2757 }, + { 0x2795, 0x2797 }, + { 0x27b0, 0x27b0 }, + { 0x27bf, 0x27bf }, + { 0x2b1b, 0x2b1c }, + { 0x2b50, 0x2b50 }, + { 0x2b55, 0x2b55 }, + { 0x2e80, 0x2e99 }, + { 0x2e9b, 0x2ef3 }, + { 0x2f00, 0x2fd5 }, + { 0x2ff0, 0x2ffb }, + { 0x3000, 0x303e }, + { 0x3041, 0x3096 }, + { 0x3099, 0x30ff }, + { 0x3105, 0x312d }, + { 0x3131, 0x318e }, + { 0x3190, 0x31ba }, + { 0x31c0, 0x31e3 }, + { 0x31f0, 0x321e }, + { 0x3220, 0x3247 }, + { 0x3250, 0x32fe }, + { 0x3300, 0x4dbf }, + { 0x4e00, 0xa48c }, + { 0xa490, 0xa4c6 }, + { 0xa960, 0xa97c }, + { 0xac00, 0xd7a3 }, + { 0xf900, 0xfaff }, + { 0xfe10, 0xfe19 }, + { 0xfe30, 0xfe52 }, + { 0xfe54, 0xfe66 }, + { 0xfe68, 0xfe6b }, + { 0xff01, 0xff60 }, + { 0xffe0, 0xffe6 }, + { 0x16fe0, 0x16fe0 }, + { 0x17000, 0x187ec }, + { 0x18800, 0x18af2 }, + { 0x1b000, 0x1b001 }, + { 0x1f004, 0x1f004 }, + { 0x1f0cf, 0x1f0cf }, + { 0x1f18e, 0x1f18e }, + { 0x1f191, 0x1f19a }, + { 0x1f200, 0x1f202 }, + { 0x1f210, 0x1f23b }, + { 0x1f240, 0x1f248 }, + { 0x1f250, 0x1f251 }, + { 0x1f300, 0x1f320 }, + { 0x1f32d, 0x1f335 }, + { 0x1f337, 0x1f37c }, + { 0x1f37e, 0x1f393 }, + { 0x1f3a0, 0x1f3ca }, + { 0x1f3cf, 0x1f3d3 }, + { 0x1f3e0, 0x1f3f0 }, + { 0x1f3f4, 0x1f3f4 }, + { 0x1f3f8, 0x1f43e }, + { 0x1f440, 0x1f440 }, + { 0x1f442, 0x1f4fc }, + { 0x1f4ff, 0x1f53d }, + { 0x1f54b, 0x1f54e }, + { 0x1f550, 0x1f567 }, + { 0x1f57a, 0x1f57a }, + { 0x1f595, 0x1f596 }, + { 0x1f5a4, 0x1f5a4 }, + { 0x1f5fb, 0x1f64f }, + { 0x1f680, 0x1f6c5 }, + { 0x1f6cc, 0x1f6cc }, + { 0x1f6d0, 0x1f6d2 }, + { 0x1f6eb, 0x1f6ec }, + { 0x1f6f4, 0x1f6f6 }, + { 0x1f910, 0x1f91e }, + { 0x1f920, 0x1f927 }, + { 0x1f930, 0x1f930 }, + { 0x1f933, 0x1f93e }, + { 0x1f940, 0x1f94b }, + { 0x1f950, 0x1f95e }, + { 0x1f980, 0x1f991 }, + { 0x1f9c0, 0x1f9c0 }, diff --git a/extlib/libvterm/keyboard.c b/extlib/libvterm/keyboard.c new file mode 100644 index 0000000..3deac16 --- /dev/null +++ b/extlib/libvterm/keyboard.c @@ -0,0 +1,228 @@ +#include "vterm_internal.h" + +#include + +#include "utf8.h" + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) { + /* The shift modifier is never important for Unicode characters + * apart from Space + */ + if (c != ' ') + mod &= ~VTERM_MOD_SHIFT; + + if (mod == 0) { + // Normal text - ignore just shift + char str[6]; + int seqlen = fill_utf8(c, str); + vterm_push_output_bytes(vt, str, seqlen); + return; + } + + int needs_CSIu; + switch (c) { + /* Special Ctrl- letters that can't be represented elsewise */ + case 'i': + case 'j': + case 'm': + case '[': + needs_CSIu = 1; + break; + /* Ctrl-\ ] ^ _ don't need CSUu */ + case '\\': + case ']': + case '^': + case '_': + needs_CSIu = 0; + break; + /* Shift-space needs CSIu */ + case ' ': + needs_CSIu = !!(mod & VTERM_MOD_SHIFT); + break; + /* All other characters needs CSIu except for letters a-z */ + default: + needs_CSIu = (c < 'a' || c > 'z'); + } + + /* ALT we can just prefix with ESC; anything else requires CSI u */ + if (needs_CSIu && (mod & ~VTERM_MOD_ALT)) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod + 1); + return; + } + + if (mod & VTERM_MOD_CTRL) + c &= 0x1f; + + vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); +} + +typedef struct { + enum { + KEYCODE_NONE, + KEYCODE_LITERAL, + KEYCODE_TAB, + KEYCODE_ENTER, + KEYCODE_SS3, + KEYCODE_CSI, + KEYCODE_CSI_CURSOR, + KEYCODE_CSINUM, + KEYCODE_KEYPAD, + } type; + char literal; + int csinum; +} keycodes_s; + +static keycodes_s keycodes[] = { + {KEYCODE_NONE}, // NONE + + {KEYCODE_ENTER, '\r'}, // ENTER + {KEYCODE_TAB, '\t'}, // TAB + {KEYCODE_LITERAL, '\x7f'}, // BACKSPACE == ASCII DEL + {KEYCODE_LITERAL, '\x1b'}, // ESCAPE + + {KEYCODE_CSI_CURSOR, 'A'}, // UP + {KEYCODE_CSI_CURSOR, 'B'}, // DOWN + {KEYCODE_CSI_CURSOR, 'D'}, // LEFT + {KEYCODE_CSI_CURSOR, 'C'}, // RIGHT + + {KEYCODE_CSINUM, '~', 2}, // INS + {KEYCODE_CSINUM, '~', 3}, // DEL + {KEYCODE_CSI_CURSOR, 'H'}, // HOME + {KEYCODE_CSI_CURSOR, 'F'}, // END + {KEYCODE_CSINUM, '~', 5}, // PAGEUP + {KEYCODE_CSINUM, '~', 6}, // PAGEDOWN +}; + +static keycodes_s keycodes_fn[] = { + {KEYCODE_NONE}, // F0 - shouldn't happen + {KEYCODE_SS3, 'P'}, // F1 + {KEYCODE_SS3, 'Q'}, // F2 + {KEYCODE_SS3, 'R'}, // F3 + {KEYCODE_SS3, 'S'}, // F4 + {KEYCODE_CSINUM, '~', 15}, // F5 + {KEYCODE_CSINUM, '~', 17}, // F6 + {KEYCODE_CSINUM, '~', 18}, // F7 + {KEYCODE_CSINUM, '~', 19}, // F8 + {KEYCODE_CSINUM, '~', 20}, // F9 + {KEYCODE_CSINUM, '~', 21}, // F10 + {KEYCODE_CSINUM, '~', 23}, // F11 + {KEYCODE_CSINUM, '~', 24}, // F12 +}; + +static keycodes_s keycodes_kp[] = { + {KEYCODE_KEYPAD, '0', 'p'}, // KP_0 + {KEYCODE_KEYPAD, '1', 'q'}, // KP_1 + {KEYCODE_KEYPAD, '2', 'r'}, // KP_2 + {KEYCODE_KEYPAD, '3', 's'}, // KP_3 + {KEYCODE_KEYPAD, '4', 't'}, // KP_4 + {KEYCODE_KEYPAD, '5', 'u'}, // KP_5 + {KEYCODE_KEYPAD, '6', 'v'}, // KP_6 + {KEYCODE_KEYPAD, '7', 'w'}, // KP_7 + {KEYCODE_KEYPAD, '8', 'x'}, // KP_8 + {KEYCODE_KEYPAD, '9', 'y'}, // KP_9 + {KEYCODE_KEYPAD, '*', 'j'}, // KP_MULT + {KEYCODE_KEYPAD, '+', 'k'}, // KP_PLUS + {KEYCODE_KEYPAD, ',', 'l'}, // KP_COMMA + {KEYCODE_KEYPAD, '-', 'm'}, // KP_MINUS + {KEYCODE_KEYPAD, '.', 'n'}, // KP_PERIOD + {KEYCODE_KEYPAD, '/', 'o'}, // KP_DIVIDE + {KEYCODE_KEYPAD, '\n', 'M'}, // KP_ENTER + {KEYCODE_KEYPAD, '=', 'X'}, // KP_EQUAL +}; + +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) { + if (key == VTERM_KEY_NONE) + return; + + keycodes_s k; + if (key < VTERM_KEY_FUNCTION_0) { + if (key >= sizeof(keycodes) / sizeof(keycodes[0])) + return; + k = keycodes[key]; + } else if (key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) { + if ((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn) / sizeof(keycodes_fn[0])) + return; + k = keycodes_fn[key - VTERM_KEY_FUNCTION_0]; + } else if (key >= VTERM_KEY_KP_0) { + if ((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp) / sizeof(keycodes_kp[0])) + return; + k = keycodes_kp[key - VTERM_KEY_KP_0]; + } + + switch (k.type) { + case KEYCODE_NONE: + break; + + case KEYCODE_TAB: + /* Shift-Tab is CSI Z but plain Tab is 0x09 */ + if (mod == VTERM_MOD_SHIFT) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); + else if (mod & VTERM_MOD_SHIFT) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod + 1); + else + goto case_LITERAL; + break; + + case KEYCODE_ENTER: + /* Enter is CRLF in newline mode, but just LF in linefeed */ + if (vt->state->mode.newline) + vterm_push_output_sprintf(vt, "\r\n"); + else + goto case_LITERAL; + break; + + case KEYCODE_LITERAL: + case_LITERAL: + if (mod & (VTERM_MOD_SHIFT | VTERM_MOD_CTRL)) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod + 1); + else + vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); + break; + + case KEYCODE_SS3: + case_SS3: + if (mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal); + else + goto case_CSI; + break; + + case KEYCODE_CSI: + case_CSI: + if (mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal); + else + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal); + break; + + case KEYCODE_CSINUM: + if (mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal); + else + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal); + break; + + case KEYCODE_CSI_CURSOR: + if (vt->state->mode.cursor) + goto case_SS3; + else + goto case_CSI; + + case KEYCODE_KEYPAD: + if (vt->state->mode.keypad) { + k.literal = k.csinum; + goto case_SS3; + } else + goto case_LITERAL; + } +} + +void vterm_keyboard_start_paste(VTerm *vt) { + if (vt->state->mode.bracketpaste) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~"); +} + +void vterm_keyboard_end_paste(VTerm *vt) { + if (vt->state->mode.bracketpaste) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~"); +} diff --git a/extlib/libvterm/mouse.c b/extlib/libvterm/mouse.c new file mode 100644 index 0000000..e1e9ad8 --- /dev/null +++ b/extlib/libvterm/mouse.c @@ -0,0 +1,89 @@ +#include "vterm_internal.h" + +#include "utf8.h" + +static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) { + modifiers <<= 2; + + switch (state->mouse_protocol) { + case MOUSE_X10: + if (col + 0x21 > 0xff) + col = 0xff - 0x21; + if (row + 0x21 > 0xff) + row = 0xff - 0x21; + + if (!pressed) + code = 3; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", (code | modifiers) + 0x20, col + 0x21, row + 0x21); + break; + + case MOUSE_UTF8: { + char utf8[18]; + size_t len = 0; + + if (!pressed) + code = 3; + + len += fill_utf8((code | modifiers) + 0x20, utf8 + len); + len += fill_utf8(col + 0x21, utf8 + len); + len += fill_utf8(row + 0x21, utf8 + len); + utf8[len] = 0; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8); + } break; + + case MOUSE_SGR: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c", code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); + break; + + case MOUSE_RXVT: + if (!pressed) + code = 3; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM", code | modifiers, col + 1, row + 1); + break; + } +} + +void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod) { + VTermState *state = vt->state; + + if (col == state->mouse_col && row == state->mouse_row) + return; + + state->mouse_col = col; + state->mouse_row = row; + + if ((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || + (state->mouse_flags & MOUSE_WANT_MOVE)) { + int button = state->mouse_buttons & 0x01 ? 1 : + state->mouse_buttons & 0x02 ? 2 : + state->mouse_buttons & 0x04 ? 3 : + 4; + output_mouse(state, button - 1 + 0x20, 1, mod, col, row); + } +} + +void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) { + VTermState *state = vt->state; + + int old_buttons = state->mouse_buttons; + + if (button > 0 && button <= 3) { + if (pressed) + state->mouse_buttons |= (1 << (button - 1)); + else + state->mouse_buttons &= ~(1 << (button - 1)); + } + + /* Most of the time we don't get button releases from 4/5 */ + if (state->mouse_buttons == old_buttons && button < 4) + return; + + if (button < 4) { + output_mouse(state, button - 1, pressed, mod, state->mouse_col, state->mouse_row); + } else if (button < 6) { + output_mouse(state, button - 4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row); + } +} diff --git a/extlib/libvterm/parser.c b/extlib/libvterm/parser.c new file mode 100644 index 0000000..f91d917 --- /dev/null +++ b/extlib/libvterm/parser.c @@ -0,0 +1,329 @@ +#include "vterm_internal.h" + +#include +#include + +#include "../../runtime/printf.h" + +#undef DEBUG_PARSER + +static bool is_intermed(unsigned char c) { + return c >= 0x20 && c <= 0x2f; +} + +static void do_control(VTerm *vt, unsigned char control) { + if (vt->parser.callbacks && vt->parser.callbacks->control) + if ((*vt->parser.callbacks->control)(control, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control); +} + +static void do_csi(VTerm *vt, char command) { +#ifdef DEBUG_PARSER + printf("Parsed CSI args as:\n", arglen, args); + printf(" leader: %s\n", vt->parser.csi_leader); + for (int argi = 0; argi < vt->parser.csi_argi; argi++) { + printf(" %lu", CSI_ARG(vt->parser.csi_args[argi])); + if (!CSI_ARG_HAS_MORE(vt->parser.csi_args[argi])) + printf("\n"); + printf(" intermed: %s\n", vt->parser.intermed); + } +#endif + + if (vt->parser.callbacks && vt->parser.callbacks->csi) + if ((*vt->parser.callbacks->csi)( + vt->parser.csi_leaderlen ? vt->parser.csi_leader : NULL, + vt->parser.csi_args, + vt->parser.csi_argi, + vt->parser.intermedlen ? vt->parser.intermed : NULL, + command, + vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled CSI %c\n", command); +} + +static void do_escape(VTerm *vt, char command) { + char seq[INTERMED_MAX + 1]; + + size_t len = vt->parser.intermedlen; + strncpy(seq, vt->parser.intermed, len); + seq[len++] = command; + seq[len] = 0; + + if (vt->parser.callbacks && vt->parser.callbacks->escape) + if ((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command); +} + +static void append_strbuffer(VTerm *vt, const char *str, size_t len) { + if (len > vt->parser.strbuffer_len - vt->parser.strbuffer_cur) { + len = vt->parser.strbuffer_len - vt->parser.strbuffer_cur; + DEBUG_LOG("Truncating strbuffer preserve to %zd bytes\n", len); + } + + if (len > 0) { + strncpy(vt->parser.strbuffer + vt->parser.strbuffer_cur, str, len); + vt->parser.strbuffer_cur += len; + } +} + +static void start_string(VTerm *vt, VTermParserStringType type) { + vt->parser.stringtype = type; + + vt->parser.strbuffer_cur = 0; +} + +static void more_string(VTerm *vt, const char *str, size_t len) { + append_strbuffer(vt, str, len); +} + +static void done_string(VTerm *vt, const char *str, size_t len) { + if (vt->parser.strbuffer_cur) { + if (str) + append_strbuffer(vt, str, len); + + str = vt->parser.strbuffer; + len = vt->parser.strbuffer_cur; + } else if (!str) { + DEBUG_LOG("parser.c: TODO: No strbuffer _and_ no final fragment???\n"); + len = 0; + } + + switch (vt->parser.stringtype) { + case VTERM_PARSER_OSC: + if (vt->parser.callbacks && vt->parser.callbacks->osc) + if ((*vt->parser.callbacks->osc)(str, len, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled OSC %.*s\n", (int)len, str); + return; + + case VTERM_PARSER_DCS: + if (vt->parser.callbacks && vt->parser.callbacks->dcs) + if ((*vt->parser.callbacks->dcs)(str, len, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)len, str); + return; + + case VTERM_N_PARSER_TYPES: + return; + } +} + +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) { + size_t pos = 0; + const char *string_start; + + switch (vt->parser.state) { + case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case ESC: + string_start = NULL; + break; + case STRING: + case ESC_IN_STRING: + string_start = bytes; + break; + } + +#define ENTER_STRING_STATE(st) \ + do { \ + vt->parser.state = STRING; \ + string_start = bytes + pos + 1; \ + } while (0) +#define ENTER_STATE(st) \ + do { \ + vt->parser.state = st; \ + string_start = NULL; \ + } while (0) +#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) + + for (; pos < len; pos++) { + unsigned char c = bytes[pos]; + + if (c == 0x00 || c == 0x7f) { // NUL, DEL + if (vt->parser.state >= STRING) { + more_string(vt, string_start, bytes + pos - string_start); + string_start = bytes + pos + 1; + } + continue; + } + if (c == 0x18 || c == 0x1a) { // CAN, SUB + ENTER_NORMAL_STATE(); + continue; + } else if (c == 0x1b) { // ESC + vt->parser.intermedlen = 0; + if (vt->parser.state == STRING) + vt->parser.state = ESC_IN_STRING; + else + ENTER_STATE(ESC); + continue; + } else if (c == 0x07 && // BEL, can stand for ST in OSC or DCS state + vt->parser.state == STRING) { + // fallthrough + } else if (c < 0x20) { // other C0 + if (vt->parser.state >= STRING) + more_string(vt, string_start, bytes + pos - string_start); + do_control(vt, c); + if (vt->parser.state >= STRING) + string_start = bytes + pos + 1; + continue; + } + // else fallthrough + + switch (vt->parser.state) { + case ESC_IN_STRING: + if (c == 0x5c) { // ST + vt->parser.state = STRING; + done_string(vt, string_start, bytes + pos - string_start - 1); + ENTER_NORMAL_STATE(); + break; + } + vt->parser.state = ESC; + // else fallthrough + + case ESC: + switch (c) { + case 0x50: // DCS + start_string(vt, VTERM_PARSER_DCS); + ENTER_STRING_STATE(); + break; + case 0x5b: // CSI + vt->parser.csi_leaderlen = 0; + ENTER_STATE(CSI_LEADER); + break; + case 0x5d: // OSC + start_string(vt, VTERM_PARSER_OSC); + ENTER_STRING_STATE(); + break; + default: + if (is_intermed(c)) { + if (vt->parser.intermedlen < INTERMED_MAX - 1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + } else if (!vt->parser.intermedlen && c >= 0x40 && c < 0x60) { + do_control(vt, c + 0x40); + ENTER_NORMAL_STATE(); + } else if (c >= 0x30 && c < 0x7f) { + do_escape(vt, c); + ENTER_NORMAL_STATE(); + } else { + DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c); + } + } + break; + + case CSI_LEADER: + /* Extract leader bytes 0x3c to 0x3f */ + if (c >= 0x3c && c <= 0x3f) { + if (vt->parser.csi_leaderlen < CSI_LEADER_MAX - 1) + vt->parser.csi_leader[vt->parser.csi_leaderlen++] = c; + break; + } + + /* else fallthrough */ + vt->parser.csi_leader[vt->parser.csi_leaderlen] = 0; + + vt->parser.csi_argi = 0; + vt->parser.csi_args[0] = CSI_ARG_MISSING; + vt->parser.state = CSI_ARGS; + + /* fallthrough */ + case CSI_ARGS: + /* Numerical value of argument */ + if (c >= '0' && c <= '9') { + if (vt->parser.csi_args[vt->parser.csi_argi] == CSI_ARG_MISSING) + vt->parser.csi_args[vt->parser.csi_argi] = 0; + vt->parser.csi_args[vt->parser.csi_argi] *= 10; + vt->parser.csi_args[vt->parser.csi_argi] += c - '0'; + break; + } + if (c == ':') { + vt->parser.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE; + c = ';'; + } + if (c == ';') { + vt->parser.csi_argi++; + vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING; + break; + } + + /* else fallthrough */ + vt->parser.csi_argi++; + vt->parser.intermedlen = 0; + vt->parser.state = CSI_INTERMED; + case CSI_INTERMED: + if (is_intermed(c)) { + if (vt->parser.intermedlen < INTERMED_MAX - 1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + break; + } else if (c == 0x1b) { + /* ESC in CSI cancels */ + } else if (c >= 0x40 && c <= 0x7e) { + vt->parser.intermed[vt->parser.intermedlen] = 0; + do_csi(vt, c); + } + /* else was invalid CSI */ + + ENTER_NORMAL_STATE(); + break; + + case STRING: + if (c == 0x07 || (c == 0x9c && !vt->mode.utf8)) { + done_string(vt, string_start, bytes + pos - string_start); + ENTER_NORMAL_STATE(); + } + break; + + case NORMAL: + if (c >= 0x80 && c < 0xa0 && !vt->mode.utf8) { + switch (c) { + case 0x90: // DCS + start_string(vt, VTERM_PARSER_DCS); + ENTER_STRING_STATE(); + break; + case 0x9b: // CSI + ENTER_STATE(CSI_LEADER); + break; + case 0x9d: // OSC + start_string(vt, VTERM_PARSER_OSC); + ENTER_STRING_STATE(); + break; + default: + do_control(vt, c); + break; + } + } else { + size_t eaten = 0; + if (vt->parser.callbacks && vt->parser.callbacks->text) + eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); + + if (!eaten) { + DEBUG_LOG("libvterm: Text callback did not consume any input\n"); + /* force it to make progress */ + eaten = 1; + } + + pos += (eaten - 1); // we'll ++ it again in a moment + } + break; + } + } + + return len; +} + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) { + vt->parser.callbacks = callbacks; + vt->parser.cbdata = user; +} + +void *vterm_parser_get_cbdata(VTerm *vt) { + return vt->parser.cbdata; +} diff --git a/extlib/libvterm/pen.c b/extlib/libvterm/pen.c new file mode 100644 index 0000000..426a4fa --- /dev/null +++ b/extlib/libvterm/pen.c @@ -0,0 +1,586 @@ +#include "vterm_internal.h" + +#include + +/** + * Structure used to store RGB triples without the additional metadata stored in + * VTermColor. + */ +typedef struct { + uint8_t red, green, blue; +} VTermRGB; + +static const VTermRGB ansi_colors[] = { + /* R G B */ + {0, 0, 0}, // black + {224, 0, 0}, // red + {0, 224, 0}, // green + {224, 224, 0}, // yellow + {0, 0, 224}, // blue + {224, 0, 224}, // magenta + {0, 224, 224}, // cyan + {224, 224, 224}, // white == light grey + + // high intensity + {128, 128, 128}, // black + {255, 64, 64}, // red + {64, 255, 64}, // green + {255, 255, 64}, // yellow + {64, 64, 255}, // blue + {255, 64, 255}, // magenta + {64, 255, 255}, // cyan + {255, 255, 255}, // white for real +}; + +static int ramp6[] = { + 0x00, + 0x33, + 0x66, + 0x99, + 0xCC, + 0xFF, +}; + +static int ramp24[] = { + 0x00, + 0x0B, + 0x16, + 0x21, + 0x2C, + 0x37, + 0x42, + 0x4D, + 0x58, + 0x63, + 0x6E, + 0x79, + 0x85, + 0x90, + 0x9B, + 0xA6, + 0xB1, + 0xBC, + 0xC7, + 0xD2, + 0xDD, + 0xE8, + 0xF3, + 0xFF, +}; + +static void lookup_default_colour_ansi(long idx, VTermColor *col) { + if (idx >= 0 && idx < 16) { + vterm_color_rgb( + col, + ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue); + } +} + +static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) { + if (index >= 0 && index < 16) { + *col = state->colors[index]; + return true; + } + + return false; +} + +static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col) { + if (index >= 0 && index < 16) { + // Normal 8 colours or high intensity - parse as palette 0 + return lookup_colour_ansi(state, index, col); + } else if (index >= 16 && index < 232) { + // 216-colour cube + index -= 16; + + vterm_color_rgb(col, ramp6[index / 6 / 6 % 6], ramp6[index / 6 % 6], ramp6[index % 6]); + + return true; + } else if (index >= 232 && index < 256) { + // 24 greyscales + index -= 232; + + vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]); + + return true; + } + + return false; +} + +static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col) { + switch (palette) { + case 2: // RGB mode - 3 args contain colour values directly + if (argcount < 3) + return argcount; + + vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2])); + + return 3; + + case 5: // XTerm 256-colour mode + if (!argcount || CSI_ARG_IS_MISSING(args[0])) { + return argcount ? 1 : 0; + } + + vterm_color_indexed(col, args[0]); + + return argcount ? 1 : 0; + + default: + DEBUG_LOG("Unrecognised colour palette %d\n", palette); + return 0; + } +} + +// Some conveniences + +static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) { +#ifdef DEBUG + if (type != vterm_get_attr_type(attr)) { + DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", attr, vterm_get_attr_type(attr), type); + return; + } +#endif + if (state->callbacks && state->callbacks->setpenattr) + (*state->callbacks->setpenattr)(attr, val, state->cbdata); +} + +static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) { + VTermValue val = {.boolean = boolean}; + setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val); +} + +static void setpenattr_int(VTermState *state, VTermAttr attr, int number) { + VTermValue val = {.number = number}; + setpenattr(state, attr, VTERM_VALUETYPE_INT, &val); +} + +static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) { + VTermValue val = {.color = color}; + setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val); +} + +static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) { + VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; + + vterm_color_indexed(colp, col); + + setpenattr_col(state, attr, *colp); +} + +INTERNAL void vterm_state_newpen(VTermState *state) { + // 90% grey so that pure white is brighter + vterm_color_rgb(&state->default_fg, 240, 240, 240); + vterm_color_rgb(&state->default_bg, 0, 0, 0); + vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg); + + for (int col = 0; col < 16; col++) + lookup_default_colour_ansi(col, &state->colors[col]); +} + +INTERNAL void vterm_state_resetpen(VTermState *state) { + state->pen.bold = 0; + setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + state->pen.underline = 0; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); + state->pen.italic = 0; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + state->pen.blink = 0; + setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + state->pen.reverse = 0; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + state->pen.strike = 0; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + state->pen.font = 0; + setpenattr_int(state, VTERM_ATTR_FONT, 0); + + state->pen.fg = state->default_fg; + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); + state->pen.bg = state->default_bg; + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); +} + +INTERNAL void vterm_state_savepen(VTermState *state, int save) { + if (save) { + state->saved.pen = state->pen; + } else { + state->pen = state->saved.pen; + + setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold); + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic); + setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink); + setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse); + setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike); + setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + } +} + +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) { + /* First make sure that the two colours are of the same type (RGB/Indexed) */ + if (a->type != b->type) { + return false; + } + + /* Depending on the type inspect the corresponding members */ + if (VTERM_COLOR_IS_INDEXED(a)) { + return a->indexed.idx == b->indexed.idx; + } else if (VTERM_COLOR_IS_RGB(a)) { + return (a->rgb.red == b->rgb.red) && (a->rgb.green == b->rgb.green) && (a->rgb.blue == b->rgb.blue); + } + + return 0; +} + +void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg) { + *default_fg = state->default_fg; + *default_bg = state->default_bg; +} + +void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col) { + lookup_colour_palette(state, index, col); +} + +void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg) { + /* Copy the given colors */ + state->default_fg = *default_fg; + state->default_bg = *default_bg; + + /* Make sure the correct type flags are set */ + state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK) | VTERM_COLOR_DEFAULT_FG; + state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK) | VTERM_COLOR_DEFAULT_BG; +} + +void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col) { + if (index >= 0 && index < 16) + state->colors[index] = *col; +} + +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col) { + if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */ + lookup_colour_palette(state, col->indexed.idx, col); + } + col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */ +} + +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) { + state->bold_is_highbright = bold_is_highbright; +} + +INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount) { + // SGR - ECMA-48 8.3.117 + + int argi = 0; + int value; + + while (argi < argcount) { + // This logic is easier to do 'done' backwards; set it true, and make it + // false again in the 'default' case + int done = 1; + + long arg; + switch (arg = CSI_ARG(args[argi])) { + case CSI_ARG_MISSING: + case 0: // Reset + vterm_state_resetpen(state); + break; + + case 1: { // Bold on + const VTermColor *fg = &state->pen.fg; + state->pen.bold = 1; + setpenattr_bool(state, VTERM_ATTR_BOLD, 1); + if (!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright) + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0)); + break; + } + + case 3: // Italic on + state->pen.italic = 1; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 1); + break; + + case 4: // Underline + state->pen.underline = VTERM_UNDERLINE_SINGLE; + if (CSI_ARG_HAS_MORE(args[argi])) { + argi++; + switch (CSI_ARG(args[argi])) { + case 0: + state->pen.underline = 0; + break; + case 1: + state->pen.underline = VTERM_UNDERLINE_SINGLE; + break; + case 2: + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + break; + case 3: + state->pen.underline = VTERM_UNDERLINE_CURLY; + break; + } + } + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 5: // Blink + state->pen.blink = 1; + setpenattr_bool(state, VTERM_ATTR_BLINK, 1); + break; + + case 7: // Reverse on + state->pen.reverse = 1; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 1); + break; + + case 9: // Strikethrough on + state->pen.strike = 1; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 1); + break; + + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: // Select font + state->pen.font = CSI_ARG(args[argi]) - 10; + setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); + break; + + case 21: // Underline double + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 22: // Bold off + state->pen.bold = 0; + setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + break; + + case 23: // Italic and Gothic (currently unsupported) off + state->pen.italic = 0; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + break; + + case 24: // Underline off + state->pen.underline = 0; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); + break; + + case 25: // Blink off + state->pen.blink = 0; + setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + break; + + case 27: // Reverse off + state->pen.reverse = 0; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + break; + + case 29: // Strikethrough off + state->pen.strike = 0; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + break; + + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: // Foreground colour palette + value = CSI_ARG(args[argi]) - 30; + if (state->pen.bold && state->bold_is_highbright) + value += 8; + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 38: // Foreground colour alternative palette + if (argcount - argi < 1) + return; + argi += 1 + lookup_colour(state, CSI_ARG(args[argi + 1]), args + argi + 2, argcount - argi - 2, &state->pen.fg); + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 39: // Foreground colour default + state->pen.fg = state->default_fg; + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: // Background colour palette + value = CSI_ARG(args[argi]) - 40; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + case 48: // Background colour alternative palette + if (argcount - argi < 1) + return; + argi += 1 + lookup_colour(state, CSI_ARG(args[argi + 1]), args + argi + 2, argcount - argi - 2, &state->pen.bg); + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 49: // Default background + state->pen.bg = state->default_bg; + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 90: + case 91: + case 92: + case 93: + case 94: + case 95: + case 96: + case 97: // Foreground colour high-intensity palette + value = CSI_ARG(args[argi]) - 90 + 8; + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 100: + case 101: + case 102: + case 103: + case 104: + case 105: + case 106: + case 107: // Background colour high-intensity palette + value = CSI_ARG(args[argi]) - 100 + 8; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + default: + done = 0; + break; + } + + if (!done) + DEBUG_LOG("libvterm: Unhandled CSI SGR %lu\n", arg); + + while (CSI_ARG_HAS_MORE(args[argi++])) + ; + } +} + +static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg) { + /* Do nothing if the given color is the default color */ + if ((fg && VTERM_COLOR_IS_DEFAULT_FG(col)) || + (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) { + return argi; + } + + /* Decide whether to send an indexed color or an RGB color */ + if (VTERM_COLOR_IS_INDEXED(col)) { + const uint8_t idx = col->indexed.idx; + if (idx < 8) { + args[argi++] = (idx + (fg ? 30 : 40)); + } else if (idx < 16) { + args[argi++] = (idx - 8 + (fg ? 90 : 100)); + } else { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 5; + args[argi++] = idx; + } + } else if (VTERM_COLOR_IS_RGB(col)) { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 2; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green; + args[argi++] = col->rgb.blue; + } + return argi; +} + +INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount) { + int argi = 0; + + if (state->pen.bold) + args[argi++] = 1; + + if (state->pen.italic) + args[argi++] = 3; + + if (state->pen.underline == VTERM_UNDERLINE_SINGLE) + args[argi++] = 4; + if (state->pen.underline == VTERM_UNDERLINE_CURLY) + args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3; + + if (state->pen.blink) + args[argi++] = 5; + + if (state->pen.reverse) + args[argi++] = 7; + + if (state->pen.strike) + args[argi++] = 9; + + if (state->pen.font) + args[argi++] = 10 + state->pen.font; + + if (state->pen.underline == VTERM_UNDERLINE_DOUBLE) + args[argi++] = 21; + + argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true); + + argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false); + + return argi; +} + +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) { + switch (attr) { + case VTERM_ATTR_BOLD: + val->boolean = state->pen.bold; + return 1; + + case VTERM_ATTR_UNDERLINE: + val->number = state->pen.underline; + return 1; + + case VTERM_ATTR_ITALIC: + val->boolean = state->pen.italic; + return 1; + + case VTERM_ATTR_BLINK: + val->boolean = state->pen.blink; + return 1; + + case VTERM_ATTR_REVERSE: + val->boolean = state->pen.reverse; + return 1; + + case VTERM_ATTR_STRIKE: + val->boolean = state->pen.strike; + return 1; + + case VTERM_ATTR_FONT: + val->number = state->pen.font; + return 1; + + case VTERM_ATTR_FOREGROUND: + val->color = state->pen.fg; + return 1; + + case VTERM_ATTR_BACKGROUND: + val->color = state->pen.bg; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} diff --git a/extlib/libvterm/rect.h b/extlib/libvterm/rect.h new file mode 100644 index 0000000..2114f24 --- /dev/null +++ b/extlib/libvterm/rect.h @@ -0,0 +1,56 @@ +/* + * Some utility functions on VTermRect structures + */ + +#define STRFrect "(%d,%d-%d,%d)" +#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col + +/* Expand dst to contain src as well */ +static void rect_expand(VTermRect *dst, VTermRect *src) +{ + if(dst->start_row > src->start_row) dst->start_row = src->start_row; + if(dst->start_col > src->start_col) dst->start_col = src->start_col; + if(dst->end_row < src->end_row) dst->end_row = src->end_row; + if(dst->end_col < src->end_col) dst->end_col = src->end_col; +} + +/* Clip the dst to ensure it does not step outside of bounds */ +static void rect_clip(VTermRect *dst, VTermRect *bounds) +{ + if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row; + if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col; + if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row; + if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col; + /* Ensure it doesn't end up negatively-sized */ + if(dst->end_row < dst->start_row) dst->end_row = dst->start_row; + if(dst->end_col < dst->start_col) dst->end_col = dst->start_col; +} + +/* True if the two rectangles are equal */ +static int rect_equal(VTermRect *a, VTermRect *b) +{ + return (a->start_row == b->start_row) && + (a->start_col == b->start_col) && + (a->end_row == b->end_row) && + (a->end_col == b->end_col); +} + +/* True if small is contained entirely within big */ +static int rect_contains(VTermRect *big, VTermRect *small) +{ + if(small->start_row < big->start_row) return 0; + if(small->start_col < big->start_col) return 0; + if(small->end_row > big->end_row) return 0; + if(small->end_col > big->end_col) return 0; + return 1; +} + +/* True if the rectangles overlap at all */ +static int rect_intersects(VTermRect *a, VTermRect *b) +{ + if(a->start_row > b->end_row || b->start_row > a->end_row) + return 0; + if(a->start_col > b->end_col || b->start_col > a->end_col) + return 0; + return 1; +} diff --git a/extlib/libvterm/screen.c b/extlib/libvterm/screen.c new file mode 100644 index 0000000..9e94b39 --- /dev/null +++ b/extlib/libvterm/screen.c @@ -0,0 +1,877 @@ +#include "vterm_internal.h" + +#include +#include + +#include "rect.h" +#include "utf8.h" + +#define UNICODE_SPACE 0x20 +#define UNICODE_LINEFEED 0x0a + +/* State of the pen at some moment in time, also used in a cell */ +typedef struct +{ + /* After the bitfield */ + VTermColor fg, bg; + + unsigned int bold : 1; + unsigned int underline : 2; + unsigned int italic : 1; + unsigned int blink : 1; + unsigned int reverse : 1; + unsigned int strike : 1; + unsigned int font : 4; /* 0 to 9 */ + + /* Extra state storage that isn't strictly pen-related */ + unsigned int protected_cell : 1; + unsigned int dwl : 1; /* on a DECDWL or DECDHL line */ + unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */ +} ScreenPen; + +/* Internal representation of a screen cell */ +typedef struct +{ + uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; + ScreenPen pen; +} ScreenCell; + +static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell); + +struct VTermScreen { + VTerm * vt; + VTermState *state; + + const VTermScreenCallbacks *callbacks; + void * cbdata; + + VTermDamageSize damage_merge; + /* start_row == -1 => no damage */ + VTermRect damaged; + VTermRect pending_scrollrect; + int pending_scroll_downward, pending_scroll_rightward; + + int rows; + int cols; + int global_reverse; + + /* Primary and Altscreen. buffers[1] is lazily allocated as needed */ + ScreenCell *buffers[2]; + + /* buffer will == buffers[0] or buffers[1], depending on altscreen */ + ScreenCell *buffer; + + /* buffer for a single screen row used in scrollback storage callbacks */ + VTermScreenCell *sb_buffer; + + ScreenPen pen; +}; + +static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col) { + if (row < 0 || row >= screen->rows) + return NULL; + if (col < 0 || col >= screen->cols) + return NULL; + return screen->buffer + (screen->cols * row) + col; +} + +static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols) { + ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols); + + for (int row = 0; row < new_rows; row++) { + for (int col = 0; col < new_cols; col++) { + ScreenCell *new_cell = new_buffer + row * new_cols + col; + + if (buffer && row < screen->rows && col < screen->cols) + *new_cell = buffer[row * screen->cols + col]; + else { + new_cell->chars[0] = 0; + new_cell->pen = screen->pen; + } + } + } + + if (buffer) + vterm_allocator_free(screen->vt, buffer); + + return new_buffer; +} + +static void damagerect(VTermScreen *screen, VTermRect rect) { + VTermRect emit; + + switch (screen->damage_merge) { + case VTERM_DAMAGE_CELL: + /* Always emit damage event */ + emit = rect; + break; + + case VTERM_DAMAGE_ROW: + /* Emit damage longer than one row. Try to merge with existing damage in + * the same row */ + if (rect.end_row > rect.start_row + 1) { + // Bigger than 1 line - flush existing, emit this + vterm_screen_flush_damage(screen); + emit = rect; + } else if (screen->damaged.start_row == -1) { + // None stored yet + screen->damaged = rect; + return; + } else if (rect.start_row == screen->damaged.start_row) { + // Merge with the stored line + if (screen->damaged.start_col > rect.start_col) + screen->damaged.start_col = rect.start_col; + if (screen->damaged.end_col < rect.end_col) + screen->damaged.end_col = rect.end_col; + return; + } else { + // Emit the currently stored line, store a new one + emit = screen->damaged; + screen->damaged = rect; + } + break; + + case VTERM_DAMAGE_SCREEN: + case VTERM_DAMAGE_SCROLL: + /* Never emit damage event */ + if (screen->damaged.start_row == -1) + screen->damaged = rect; + else { + rect_expand(&screen->damaged, &rect); + } + return; + + default: + DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge); + return; + } + + if (screen->callbacks && screen->callbacks->damage) + (*screen->callbacks->damage)(emit, screen->cbdata); +} + +static void damagescreen(VTermScreen *screen) { + VTermRect rect = { + .start_row = 0, + .end_row = screen->rows, + .start_col = 0, + .end_col = screen->cols, + }; + + damagerect(screen, rect); +} + +static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) { + VTermScreen *screen = user; + ScreenCell * cell = getcell(screen, pos.row, pos.col); + + if (!cell) + return 0; + + int i; + for (i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) { + cell->chars[i] = info->chars[i]; + cell->pen = screen->pen; + } + if (i < VTERM_MAX_CHARS_PER_CELL) + cell->chars[i] = 0; + + for (int col = 1; col < info->width; col++) + getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1; + + VTermRect rect = { + .start_row = pos.row, + .end_row = pos.row + 1, + .start_col = pos.col, + .end_col = pos.col + info->width, + }; + + cell->pen.protected_cell = info->protected_cell; + cell->pen.dwl = info->dwl; + cell->pen.dhl = info->dhl; + + damagerect(screen, rect); + + return 1; +} + +static int moverect_internal(VTermRect dest, VTermRect src, void *user) { + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->sb_pushline && + dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner + dest.end_col == screen->cols && // full width + screen->buffer == screen->buffers[0]) { // not altscreen + VTermPos pos; + for (pos.row = 0; pos.row < src.start_row; pos.row++) { + for (pos.col = 0; pos.col < screen->cols; pos.col++) + vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); + + (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); + } + } + + int cols = src.end_col - src.start_col; + int downward = src.start_row - dest.start_row; + + int init_row, test_row, inc_row; + if (downward < 0) { + init_row = dest.end_row - 1; + test_row = dest.start_row - 1; + inc_row = -1; + } else { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + + for (int row = init_row; row != test_row; row += inc_row) + memmove(getcell(screen, row, dest.start_col), getcell(screen, row + downward, src.start_col), cols * sizeof(ScreenCell)); + + return 1; +} + +static int moverect_user(VTermRect dest, VTermRect src, void *user) { + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->moverect) { + if (screen->damage_merge != VTERM_DAMAGE_SCROLL) + // Avoid an infinite loop + vterm_screen_flush_damage(screen); + + if ((*screen->callbacks->moverect)(dest, src, screen->cbdata)) + return 1; + } + + damagerect(screen, dest); + + return 1; +} + +static int erase_internal(VTermRect rect, int selective, void *user) { + VTermScreen *screen = user; + + for (int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) { + const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row); + + for (int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if (selective && cell->pen.protected_cell) + continue; + + cell->chars[0] = 0; + cell->pen = screen->pen; + cell->pen.dwl = info->doublewidth; + cell->pen.dhl = info->doubleheight; + } + } + + return 1; +} + +static int erase_user(VTermRect rect, int selective, void *user) { + VTermScreen *screen = user; + + damagerect(screen, rect); + + return 1; +} + +static int erase(VTermRect rect, int selective, void *user) { + erase_internal(rect, selective, user); + return erase_user(rect, 0, user); +} + +static int scrollrect(VTermRect rect, int downward, int rightward, void *user) { + VTermScreen *screen = user; + + if (screen->damage_merge != VTERM_DAMAGE_SCROLL) { + vterm_scroll_rect(rect, downward, rightward, moverect_internal, erase_internal, screen); + + vterm_screen_flush_damage(screen); + + vterm_scroll_rect(rect, downward, rightward, moverect_user, erase_user, screen); + + return 1; + } + + if (screen->damaged.start_row != -1 && + !rect_intersects(&rect, &screen->damaged)) { + vterm_screen_flush_damage(screen); + } + + if (screen->pending_scrollrect.start_row == -1) { + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } else if (rect_equal(&screen->pending_scrollrect, &rect) && ((screen->pending_scroll_downward == 0 && downward == 0) || (screen->pending_scroll_rightward == 0 && rightward == 0))) { + screen->pending_scroll_downward += downward; + screen->pending_scroll_rightward += rightward; + } else { + vterm_screen_flush_damage(screen); + + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } + + vterm_scroll_rect(rect, downward, rightward, moverect_internal, erase_internal, screen); + + if (screen->damaged.start_row == -1) + return 1; + + if (rect_contains(&rect, &screen->damaged)) { + /* Scroll region entirely contains the damage; just move it */ + vterm_rect_move(&screen->damaged, -downward, -rightward); + rect_clip(&screen->damaged, &rect); + } + /* There are a number of possible cases here, but lets restrict this to only + * the common case where we might actually gain some performance by + * optimising it. Namely, a vertical scroll that neatly cuts the damage + * region in half. + */ + else if (rect.start_col <= screen->damaged.start_col && rect.end_col >= screen->damaged.end_col && rightward == 0) { + if (screen->damaged.start_row >= rect.start_row && + screen->damaged.start_row < rect.end_row) { + screen->damaged.start_row -= downward; + if (screen->damaged.start_row < rect.start_row) + screen->damaged.start_row = rect.start_row; + if (screen->damaged.start_row > rect.end_row) + screen->damaged.start_row = rect.end_row; + } + if (screen->damaged.end_row >= rect.start_row && + screen->damaged.end_row < rect.end_row) { + screen->damaged.end_row -= downward; + if (screen->damaged.end_row < rect.start_row) + screen->damaged.end_row = rect.start_row; + if (screen->damaged.end_row > rect.end_row) + screen->damaged.end_row = rect.end_row; + } + } else { + DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", ARGSrect(screen->damaged), ARGSrect(rect)); + } + + return 1; +} + +static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) { + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->movecursor) + return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); + + return 0; +} + +static int setpenattr(VTermAttr attr, VTermValue *val, void *user) { + VTermScreen *screen = user; + + switch (attr) { + case VTERM_ATTR_BOLD: + screen->pen.bold = val->boolean; + return 1; + case VTERM_ATTR_UNDERLINE: + screen->pen.underline = val->number; + return 1; + case VTERM_ATTR_ITALIC: + screen->pen.italic = val->boolean; + return 1; + case VTERM_ATTR_BLINK: + screen->pen.blink = val->boolean; + return 1; + case VTERM_ATTR_REVERSE: + screen->pen.reverse = val->boolean; + return 1; + case VTERM_ATTR_STRIKE: + screen->pen.strike = val->boolean; + return 1; + case VTERM_ATTR_FONT: + screen->pen.font = val->number; + return 1; + case VTERM_ATTR_FOREGROUND: + screen->pen.fg = val->color; + return 1; + case VTERM_ATTR_BACKGROUND: + screen->pen.bg = val->color; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} + +static int settermprop(VTermProp prop, VTermValue *val, void *user) { + VTermScreen *screen = user; + + switch (prop) { + case VTERM_PROP_ALTSCREEN: + if (val->boolean && !screen->buffers[1]) + return 0; + + screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0]; + /* only send a damage event on disable; because during enable there's an + * erase that sends a damage anyway + */ + if (!val->boolean) + damagescreen(screen); + break; + case VTERM_PROP_REVERSE: + screen->global_reverse = val->boolean; + damagescreen(screen); + break; + default:; /* ignore */ + } + + if (screen->callbacks && screen->callbacks->settermprop) + return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); + + return 1; +} + +static int bell(void *user) { + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->bell) + return (*screen->callbacks->bell)(screen->cbdata); + + return 0; +} + +static int resize(int new_rows, int new_cols, VTermPos *delta, void *user) { + VTermScreen *screen = user; + + int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]); + + int old_rows = screen->rows; + int old_cols = screen->cols; + + if (!is_altscreen && new_rows < old_rows) { + // Fewer rows - determine if we're going to scroll at all, and if so, push + // those lines to scrollback + VTermPos pos = {0, 0}; + VTermPos cursor = screen->state->pos; + // Find the first blank row after the cursor. + for (pos.row = old_rows - 1; pos.row >= new_rows; pos.row--) + if (!vterm_screen_is_eol(screen, pos) || cursor.row == pos.row) + break; + + int first_blank_row = pos.row + 1; + if (first_blank_row > new_rows) { + VTermRect rect = { + .start_row = 0, + .end_row = old_rows, + .start_col = 0, + .end_col = old_cols, + }; + scrollrect(rect, first_blank_row - new_rows, 0, user); + vterm_screen_flush_damage(screen); + + delta->row -= first_blank_row - new_rows; + } + } + + screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols); + if (screen->buffers[1]) + screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols); + + screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0]; + + screen->rows = new_rows; + screen->cols = new_cols; + + if (screen->sb_buffer) + vterm_allocator_free(screen->vt, screen->sb_buffer); + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); + + if (new_cols > old_cols) { + VTermRect rect = { + .start_row = 0, + .end_row = old_rows, + .start_col = old_cols, + .end_col = new_cols, + }; + damagerect(screen, rect); + } + + if (new_rows > old_rows) { + if (!is_altscreen && screen->callbacks && screen->callbacks->sb_popline) { + int rows = new_rows - old_rows; + while (rows) { + if (!(screen->callbacks->sb_popline(screen->cols, screen->sb_buffer, screen->cbdata))) + break; + + VTermRect rect = { + .start_row = 0, + .end_row = screen->rows, + .start_col = 0, + .end_col = screen->cols, + }; + scrollrect(rect, -1, 0, user); + + VTermPos pos = {0, 0}; + for (pos.col = 0; pos.col < screen->cols; pos.col += screen->sb_buffer[pos.col].width) + vterm_screen_set_cell(screen, pos, screen->sb_buffer + pos.col); + + rect.end_row = 1; + damagerect(screen, rect); + + vterm_screen_flush_damage(screen); + + rows--; + delta->row++; + } + } + + VTermRect rect = { + .start_row = old_rows, + .end_row = new_rows, + .start_col = 0, + .end_col = new_cols, + }; + damagerect(screen, rect); + } + + if (screen->callbacks && screen->callbacks->resize) + return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); + + return 1; +} + +static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) { + VTermScreen *screen = user; + + if (newinfo->doublewidth != oldinfo->doublewidth || + newinfo->doubleheight != oldinfo->doubleheight) { + for (int col = 0; col < screen->cols; col++) { + ScreenCell *cell = getcell(screen, row, col); + cell->pen.dwl = newinfo->doublewidth; + cell->pen.dhl = newinfo->doubleheight; + } + + VTermRect rect = { + .start_row = row, + .end_row = row + 1, + .start_col = 0, + .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols, + }; + damagerect(screen, rect); + + if (newinfo->doublewidth) { + rect.start_col = screen->cols / 2; + rect.end_col = screen->cols; + + erase_internal(rect, 0, user); + } + } + + return 1; +} + +static VTermStateCallbacks state_cbs = { + .putglyph = &putglyph, + .movecursor = &movecursor, + .scrollrect = &scrollrect, + .erase = &erase, + .setpenattr = &setpenattr, + .settermprop = &settermprop, + .bell = &bell, + .resize = &resize, + .setlineinfo = &setlineinfo, +}; + +static VTermScreen *screen_new(VTerm *vt) { + VTermState *state = vterm_obtain_state(vt); + if (!state) + return NULL; + + VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); + int rows, cols; + + vterm_get_size(vt, &rows, &cols); + + screen->vt = vt; + screen->state = state; + + screen->damage_merge = VTERM_DAMAGE_CELL; + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + + screen->rows = rows; + screen->cols = cols; + + screen->callbacks = NULL; + screen->cbdata = NULL; + + screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols); + + screen->buffer = screen->buffers[0]; + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols); + + vterm_state_set_callbacks(screen->state, &state_cbs, screen); + + return screen; +} + +INTERNAL void vterm_screen_free(VTermScreen *screen) { + vterm_allocator_free(screen->vt, screen->buffers[0]); + if (screen->buffers[1]) + vterm_allocator_free(screen->vt, screen->buffers[1]); + + vterm_allocator_free(screen->vt, screen->sb_buffer); + + vterm_allocator_free(screen->vt, screen); +} + +void vterm_screen_reset(VTermScreen *screen, int hard) { + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + vterm_state_reset(screen->state, hard); + vterm_screen_flush_damage(screen); +} + +static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect) { + size_t outpos = 0; + int padding = 0; + +#define PUT(c) \ + if (utf8) { \ + size_t thislen = utf8_seqlen(c); \ + if (buffer && outpos + thislen <= len) \ + outpos += fill_utf8((c), (char *)buffer + outpos); \ + else \ + outpos += thislen; \ + } else { \ + if (buffer && outpos + 1 <= len) \ + ((uint32_t *)buffer)[outpos++] = (c); \ + else \ + outpos++; \ + } + + for (int row = rect.start_row; row < rect.end_row; row++) { + for (int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if (cell->chars[0] == 0) + // Erased cell, might need a space + padding++; + else if (cell->chars[0] == (uint32_t)-1) + // Gap behind a double-width char, do nothing + ; + else { + while (padding) { + PUT(UNICODE_SPACE); + padding--; + } + for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) { + PUT(cell->chars[i]); + } + } + } + + if (row < rect.end_row - 1) { + PUT(UNICODE_LINEFEED); + padding = 0; + } + } + + return outpos; +} + +size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect) { + return _get_chars(screen, 0, chars, len, rect); +} + +size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect) { + return _get_chars(screen, 1, str, len, rect); +} + +/* Copy internal to external representation of a screen cell */ +int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) { + ScreenCell *intcell = getcell(screen, pos.row, pos.col); + if (!intcell) + return 0; + + for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { + cell->chars[i] = intcell->chars[i]; + if (!intcell->chars[i]) + break; + } + + cell->attrs.bold = intcell->pen.bold; + cell->attrs.underline = intcell->pen.underline; + cell->attrs.italic = intcell->pen.italic; + cell->attrs.blink = intcell->pen.blink; + cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; + cell->attrs.strike = intcell->pen.strike; + cell->attrs.font = intcell->pen.font; + + cell->attrs.dwl = intcell->pen.dwl; + cell->attrs.dhl = intcell->pen.dhl; + + cell->fg = intcell->pen.fg; + cell->bg = intcell->pen.bg; + + if (pos.col < (screen->cols - 1) && + getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1) + cell->width = 2; + else + cell->width = 1; + + return 1; +} + +/* Copy external to internal representation of a screen cell */ +/* static because it's only used internally for sb_popline during resize */ +static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell) { + ScreenCell *intcell = getcell(screen, pos.row, pos.col); + if (!intcell) + return 0; + + for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { + intcell->chars[i] = cell->chars[i]; + if (!cell->chars[i]) + break; + } + + intcell->pen.bold = cell->attrs.bold; + intcell->pen.underline = cell->attrs.underline; + intcell->pen.italic = cell->attrs.italic; + intcell->pen.blink = cell->attrs.blink; + intcell->pen.reverse = cell->attrs.reverse ^ screen->global_reverse; + intcell->pen.strike = cell->attrs.strike; + intcell->pen.font = cell->attrs.font; + + intcell->pen.fg = cell->fg; + intcell->pen.bg = cell->bg; + + if (cell->width == 2) + getcell(screen, pos.row, pos.col + 1)->chars[0] = (uint32_t)-1; + + return 1; +} + +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) { + /* This cell is EOL if this and every cell to the right is black */ + for (; pos.col < screen->cols; pos.col++) { + ScreenCell *cell = getcell(screen, pos.row, pos.col); + if (cell->chars[0] != 0) + return 0; + } + + return 1; +} + +VTermScreen *vterm_obtain_screen(VTerm *vt) { + if (vt->screen) + return vt->screen; + + VTermScreen *screen = screen_new(vt); + vt->screen = screen; + + return screen; +} + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) { + if (!screen->buffers[1] && altscreen) { + int rows, cols; + vterm_get_size(screen->vt, &rows, &cols); + + screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols); + } +} + +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user) { + screen->callbacks = callbacks; + screen->cbdata = user; +} + +void *vterm_screen_get_cbdata(VTermScreen *screen) { + return screen->cbdata; +} + +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user) { + vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); +} + +void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen) { + return vterm_state_get_unrecognised_fbdata(screen->state); +} + +void vterm_screen_flush_damage(VTermScreen *screen) { + if (screen->pending_scrollrect.start_row != -1) { + vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward, moverect_user, erase_user, screen); + + screen->pending_scrollrect.start_row = -1; + } + + if (screen->damaged.start_row != -1) { + if (screen->callbacks && screen->callbacks->damage) + (*screen->callbacks->damage)(screen->damaged, screen->cbdata); + + screen->damaged.start_row = -1; + } +} + +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) { + vterm_screen_flush_damage(screen); + screen->damage_merge = size; +} + +static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) { + if ((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) + return 1; + if ((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) + return 1; + if ((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) + return 1; + if ((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) + return 1; + if ((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) + return 1; + if ((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) + return 1; + if ((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) + return 1; + if ((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) + return 1; + if ((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) + return 1; + + return 0; +} + +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs) { + ScreenCell *target = getcell(screen, pos.row, pos.col); + + // TODO: bounds check + extent->start_row = pos.row; + extent->end_row = pos.row + 1; + + if (extent->start_col < 0) + extent->start_col = 0; + if (extent->end_col < 0) + extent->end_col = screen->cols; + + int col; + + for (col = pos.col - 1; col >= extent->start_col; col--) + if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) + break; + extent->start_col = col + 1; + + for (col = pos.col + 1; col < extent->end_col; col++) + if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) + break; + extent->end_col = col - 1; + + return 1; +} + +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col) { + vterm_state_convert_color_to_rgb(screen->state, col); +} diff --git a/extlib/libvterm/state.c b/extlib/libvterm/state.c new file mode 100644 index 0000000..a1288da --- /dev/null +++ b/extlib/libvterm/state.c @@ -0,0 +1,1801 @@ +#include "vterm_internal.h" + +#include +#include + +#include "../../runtime/printf.h" + +#define strneq(a, b, n) (strncmp(a, b, n) == 0) + +#if defined(DEBUG) && DEBUG > 1 +#define DEBUG_GLYPH_COMBINE +#endif + +/* Some convenient wrappers to make callback functions easier */ + +static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos) { + VTermGlyphInfo info = { + .chars = chars, + .width = width, + .protected_cell = state->protected_cell, + .dwl = state->lineinfo[pos.row].doublewidth, + .dhl = state->lineinfo[pos.row].doubleheight, + }; + + if (state->callbacks && state->callbacks->putglyph) + if ((*state->callbacks->putglyph)(&info, pos, state->cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); +} + +static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) { + if (state->pos.col == oldpos->col && state->pos.row == oldpos->row) + return; + + if (cancel_phantom) + state->at_phantom = 0; + + if (state->callbacks && state->callbacks->movecursor) + if ((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata)) + return; +} + +static void erase(VTermState *state, VTermRect rect, int selective) { + if (state->callbacks && state->callbacks->erase) + if ((*state->callbacks->erase)(rect, selective, state->cbdata)) + return; +} + +static VTermState *vterm_state_new(VTerm *vt) { + VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); + + state->vt = vt; + + state->rows = vt->rows; + state->cols = vt->cols; + + state->mouse_col = 0; + state->mouse_row = 0; + state->mouse_buttons = 0; + + state->mouse_protocol = MOUSE_X10; + + state->callbacks = NULL; + state->cbdata = NULL; + + vterm_state_newpen(state); + + state->bold_is_highbright = 0; + + return state; +} + +INTERNAL void vterm_state_free(VTermState *state) { + vterm_allocator_free(state->vt, state->tabstops); + vterm_allocator_free(state->vt, state->lineinfo); + vterm_allocator_free(state->vt, state->combine_chars); + vterm_allocator_free(state->vt, state); +} + +static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) { + if (!downward && !rightward) + return; + + int rows = rect.end_row - rect.start_row; + if (downward > rows) + downward = rows; + else if (downward < -rows) + downward = -rows; + + int cols = rect.end_col - rect.start_col; + if (rightward > cols) + rightward = cols; + else if (rightward < -cols) + rightward = -cols; + + // Update lineinfo if full line + if (rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { + int height = rect.end_row - rect.start_row - abs(downward); + + if (downward > 0) + memmove(state->lineinfo + rect.start_row, state->lineinfo + rect.start_row + downward, height * sizeof(state->lineinfo[0])); + else + memmove(state->lineinfo + rect.start_row - downward, state->lineinfo + rect.start_row, height * sizeof(state->lineinfo[0])); + } + + if (state->callbacks && state->callbacks->scrollrect) + if ((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) + return; + + if (state->callbacks) + vterm_scroll_rect(rect, downward, rightward, state->callbacks->moverect, state->callbacks->erase, state->cbdata); +} + +static void linefeed(VTermState *state) { + if (state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, 1, 0); + } else if (state->pos.row < state->rows - 1) + state->pos.row++; +} + +static void grow_combine_buffer(VTermState *state) { + size_t new_size = state->combine_chars_size * 2; + uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0])); + + memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0])); + + vterm_allocator_free(state->vt, state->combine_chars); + + state->combine_chars = new_chars; + state->combine_chars_size = new_size; +} + +static void set_col_tabstop(VTermState *state, int col) { + unsigned char mask = 1 << (col & 7); + state->tabstops[col >> 3] |= mask; +} + +static void clear_col_tabstop(VTermState *state, int col) { + unsigned char mask = 1 << (col & 7); + state->tabstops[col >> 3] &= ~mask; +} + +static int is_col_tabstop(VTermState *state, int col) { + unsigned char mask = 1 << (col & 7); + return state->tabstops[col >> 3] & mask; +} + +static int is_cursor_in_scrollregion(const VTermState *state) { + if (state->pos.row < state->scrollregion_top || + state->pos.row >= SCROLLREGION_BOTTOM(state)) + return 0; + if (state->pos.col < SCROLLREGION_LEFT(state) || + state->pos.col >= SCROLLREGION_RIGHT(state)) + return 0; + + return 1; +} + +static void tab(VTermState *state, int count, int direction) { + while (count > 0) { + if (direction > 0) { + if (state->pos.col >= THISROWWIDTH(state) - 1) + return; + + state->pos.col++; + } else if (direction < 0) { + if (state->pos.col < 1) + return; + + state->pos.col--; + } + + if (is_col_tabstop(state, state->pos.col)) + count--; + } +} + +#define NO_FORCE 0 +#define FORCE 1 + +#define DWL_OFF 0 +#define DWL_ON 1 + +#define DHL_OFF 0 +#define DHL_TOP 1 +#define DHL_BOTTOM 2 + +static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) { + VTermLineInfo info = state->lineinfo[row]; + + if (dwl == DWL_OFF) + info.doublewidth = DWL_OFF; + else if (dwl == DWL_ON) + info.doublewidth = DWL_ON; + // else -1 to ignore + + if (dhl == DHL_OFF) + info.doubleheight = DHL_OFF; + else if (dhl == DHL_TOP) + info.doubleheight = DHL_TOP; + else if (dhl == DHL_BOTTOM) + info.doubleheight = DHL_BOTTOM; + + if ((state->callbacks && + state->callbacks->setlineinfo && + (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) || + force) + state->lineinfo[row] = info; +} + +static int on_text(const char bytes[], size_t len, void *user) { + VTermState *state = user; + + VTermPos oldpos = state->pos; + + // We'll have at most len codepoints + uint32_t codepoints[len]; + int npoints = 0; + size_t eaten = 0; + + VTermEncodingInstance *encoding = + state->gsingle_set ? &state->encoding[state->gsingle_set] : + !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] : + state->vt->mode.utf8 ? &state->encoding_utf8 : + &state->encoding[state->gr_set]; + + (*encoding->enc->decode)(encoding->enc, encoding->data, codepoints, &npoints, state->gsingle_set ? 1 : len, bytes, &eaten, len); + + /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet + * for even a single codepoint + */ + if (!npoints) + return eaten; + + if (state->gsingle_set && npoints) + state->gsingle_set = 0; + + int i = 0; + + /* This is a combining char. that needs to be merged with the previous + * glyph output */ + if (vterm_unicode_is_combining(codepoints[i])) { + /* See if the cursor has moved since */ + if (state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) { + /* Find where we need to append these combining chars */ + int saved_i = 0; + while (state->combine_chars[saved_i]) + saved_i++; + + /* Add extra ones */ + while (i < npoints && vterm_unicode_is_combining(codepoints[i])) { + if (saved_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[saved_i++] = codepoints[i++]; + } + if (saved_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[saved_i] = 0; + + /* Now render it */ + putglyph(state, state->combine_chars, state->combine_width, state->combine_pos); + } else { + DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); + } + } + + for (; i < npoints; i++) { + // Try to find combining characters following this + int glyph_starts = i; + int glyph_ends; + for (glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++) + if (!vterm_unicode_is_combining(codepoints[glyph_ends])) + break; + + int width = 0; + + uint32_t chars[glyph_ends - glyph_starts + 1]; + + for (; i < glyph_ends; i++) { + chars[i - glyph_starts] = codepoints[i]; + int this_width = vterm_unicode_width(codepoints[i]); + width += this_width; + } + + chars[glyph_ends - glyph_starts] = 0; + i--; + + if (state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { + linefeed(state); + state->pos.col = 0; + state->at_phantom = 0; + } + + if (state->mode.insert) { + /* TODO: This will be a little inefficient for large bodies of text, as + * it'll have to 'ICH' effectively before every glyph. We should scan + * ahead and ICH as many times as required + */ + VTermRect rect = { + .start_row = state->pos.row, + .end_row = state->pos.row + 1, + .start_col = state->pos.col, + .end_col = THISROWWIDTH(state), + }; + scroll(state, rect, 0, -1); + } + + putglyph(state, chars, width, state->pos); + + if (i == npoints - 1) { + /* End of the buffer. Save the chars in case we have to combine with + * more on the next call */ + int save_i; + for (save_i = 0; chars[save_i]; save_i++) { + if (save_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[save_i] = chars[save_i]; + } + if (save_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[save_i] = 0; + state->combine_width = width; + state->combine_pos = state->pos; + } + + if (state->pos.col + width >= THISROWWIDTH(state)) { + if (state->mode.autowrap) + state->at_phantom = 1; + } else { + state->pos.col += width; + } + } + + updatecursor(state, &oldpos, 0); + + return eaten; +} + +static int on_control(unsigned char control, void *user) { + VTermState *state = user; + + VTermPos oldpos = state->pos; + + switch (control) { + case 0x07: // BEL - ECMA-48 8.3.3 + if (state->callbacks && state->callbacks->bell) + (*state->callbacks->bell)(state->cbdata); + break; + + case 0x08: // BS - ECMA-48 8.3.5 + if (state->pos.col > 0) + state->pos.col--; + break; + + case 0x09: // HT - ECMA-48 8.3.60 + tab(state, 1, +1); + break; + + case 0x0a: // LF - ECMA-48 8.3.74 + case 0x0b: // VT + case 0x0c: // FF + linefeed(state); + if (state->mode.newline) + state->pos.col = 0; + break; + + case 0x0d: // CR - ECMA-48 8.3.15 + state->pos.col = 0; + break; + + case 0x0e: // LS1 - ECMA-48 8.3.76 + state->gl_set = 1; + break; + + case 0x0f: // LS0 - ECMA-48 8.3.75 + state->gl_set = 0; + break; + + case 0x84: // IND - DEPRECATED but implemented for completeness + linefeed(state); + break; + + case 0x85: // NEL - ECMA-48 8.3.86 + linefeed(state); + state->pos.col = 0; + break; + + case 0x88: // HTS - ECMA-48 8.3.62 + set_col_tabstop(state, state->pos.col); + break; + + case 0x8d: // RI - ECMA-48 8.3.104 + if (state->pos.row == state->scrollregion_top) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, -1, 0); + } else if (state->pos.row > 0) + state->pos.row--; + break; + + case 0x8e: // SS2 - ECMA-48 8.3.141 + state->gsingle_set = 2; + break; + + case 0x8f: // SS3 - ECMA-48 8.3.142 + state->gsingle_set = 3; + break; + + default: + if (state->fallbacks && state->fallbacks->control) + if ((*state->fallbacks->control)(control, state->fbdata)) + return 1; + + return 0; + } + + updatecursor(state, &oldpos, 1); + + return 1; +} + +static int settermprop_bool(VTermState *state, VTermProp prop, int v) { + VTermValue val = {.boolean = v}; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_int(VTermState *state, VTermProp prop, int v) { + VTermValue val = {.number = v}; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len) { + char strvalue[len + 1]; + strncpy(strvalue, str, len); + strvalue[len] = 0; + + VTermValue val = {.string = strvalue}; + return vterm_state_set_termprop(state, prop, &val); +} + +static void savecursor(VTermState *state, int save) { + if (save) { + state->saved.pos = state->pos; + state->saved.mode.cursor_visible = state->mode.cursor_visible; + state->saved.mode.cursor_blink = state->mode.cursor_blink; + state->saved.mode.cursor_shape = state->mode.cursor_shape; + + vterm_state_savepen(state, 1); + } else { + VTermPos oldpos = state->pos; + + state->pos = state->saved.pos; + + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); + + vterm_state_savepen(state, 0); + + updatecursor(state, &oldpos, 1); + } +} + +static int on_escape(const char *bytes, size_t len, void *user) { + VTermState *state = user; + + /* Easier to decode this from the first byte, even though the final + * byte terminates it + */ + switch (bytes[0]) { + case ' ': + if (len != 2) + return 0; + + switch (bytes[1]) { + case 'F': // S7C1T + state->vt->mode.ctrl8bit = 0; + break; + + case 'G': // S8C1T + state->vt->mode.ctrl8bit = 1; + break; + + default: + return 0; + } + return 2; + + case '#': + if (len != 2) + return 0; + + switch (bytes[1]) { + case '3': // DECDHL top + if (state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); + break; + + case '4': // DECDHL bottom + if (state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); + break; + + case '5': // DECSWL + if (state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); + break; + + case '6': // DECDWL + if (state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); + break; + + case '8': // DECALN + { + VTermPos pos; + uint32_t E[] = {'E', 0}; + for (pos.row = 0; pos.row < state->rows; pos.row++) + for (pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) + putglyph(state, E, 1, pos); + break; + } + + default: + return 0; + } + return 2; + + case '(': + case ')': + case '*': + case '+': // SCS + if (len != 2) + return 0; + + { + int setnum = bytes[0] - 0x28; + VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); + + if (newenc) { + state->encoding[setnum].enc = newenc; + + if (newenc->init) + (*newenc->init)(newenc, state->encoding[setnum].data); + } + } + + return 2; + + case '7': // DECSC + savecursor(state, 1); + return 1; + + case '8': // DECRC + savecursor(state, 0); + return 1; + + case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 + return 1; + + case '=': // DECKPAM + state->mode.keypad = 1; + return 1; + + case '>': // DECKPNM + state->mode.keypad = 0; + return 1; + + case 'c': // RIS - ECMA-48 8.3.105 + { + VTermPos oldpos = state->pos; + vterm_state_reset(state, 1); + if (state->callbacks && state->callbacks->movecursor) + (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata); + return 1; + } + + case 'n': // LS2 - ECMA-48 8.3.78 + state->gl_set = 2; + return 1; + + case 'o': // LS3 - ECMA-48 8.3.80 + state->gl_set = 3; + return 1; + + case '~': // LS1R - ECMA-48 8.3.77 + state->gr_set = 1; + return 1; + + case '}': // LS2R - ECMA-48 8.3.79 + state->gr_set = 2; + return 1; + + case '|': // LS3R - ECMA-48 8.3.81 + state->gr_set = 3; + return 1; + + default: + return 0; + } +} + +static void set_mode(VTermState *state, int num, int val) { + switch (num) { + case 4: // IRM - ECMA-48 7.2.10 + state->mode.insert = val; + break; + + case 20: // LNM - ANSI X3.4-1977 + state->mode.newline = val; + break; + + default: + DEBUG_LOG("libvterm: Unknown mode %d\n", num); + return; + } +} + +static void set_dec_mode(VTermState *state, int num, int val) { + switch (num) { + case 1: + state->mode.cursor = val; + break; + + case 5: // DECSCNM - screen mode + settermprop_bool(state, VTERM_PROP_REVERSE, val); + break; + + case 6: // DECOM - origin mode + { + VTermPos oldpos = state->pos; + state->mode.origin = val; + state->pos.row = state->mode.origin ? state->scrollregion_top : 0; + state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; + updatecursor(state, &oldpos, 1); + } break; + + case 7: + state->mode.autowrap = val; + break; + + case 12: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); + break; + + case 25: + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); + break; + + case 69: // DECVSSM - vertical split screen mode + // DECLRMM - left/right margin mode + state->mode.leftrightmargin = val; + if (val) { + // Setting DECVSSM must clear doublewidth/doubleheight state of every line + for (int row = 0; row < state->rows; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + } + + break; + + case 1000: + case 1002: + case 1003: + settermprop_int(state, VTERM_PROP_MOUSE, !val ? VTERM_PROP_MOUSE_NONE : (num == 1000) ? VTERM_PROP_MOUSE_CLICK : + (num == 1002) ? VTERM_PROP_MOUSE_DRAG : + VTERM_PROP_MOUSE_MOVE); + break; + + case 1004: + state->mode.report_focus = val; + break; + + case 1005: + state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; + break; + + case 1006: + state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; + break; + + case 1015: + state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; + break; + + case 1047: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + break; + + case 1048: + savecursor(state, val); + break; + + case 1049: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + savecursor(state, val); + break; + + case 2004: + state->mode.bracketpaste = val; + break; + + default: + DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); + return; + } +} + +static void request_dec_mode(VTermState *state, int num) { + int reply; + + switch (num) { + case 1: + reply = state->mode.cursor; + break; + + case 5: + reply = state->mode.screen; + break; + + case 6: + reply = state->mode.origin; + break; + + case 7: + reply = state->mode.autowrap; + break; + + case 12: + reply = state->mode.cursor_blink; + break; + + case 25: + reply = state->mode.cursor_visible; + break; + + case 69: + reply = state->mode.leftrightmargin; + break; + + case 1000: + reply = state->mouse_flags == MOUSE_WANT_CLICK; + break; + + case 1002: + reply = state->mouse_flags == (MOUSE_WANT_CLICK | MOUSE_WANT_DRAG); + break; + + case 1003: + reply = state->mouse_flags == (MOUSE_WANT_CLICK | MOUSE_WANT_MOVE); + break; + + case 1004: + reply = state->mode.report_focus; + break; + + case 1005: + reply = state->mouse_protocol == MOUSE_UTF8; + break; + + case 1006: + reply = state->mouse_protocol == MOUSE_SGR; + break; + + case 1015: + reply = state->mouse_protocol == MOUSE_RXVT; + break; + + case 1047: + reply = state->mode.alt_screen; + break; + + case 2004: + reply = state->mode.bracketpaste; + break; + + default: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); + return; + } + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); +} + +static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) { + VTermState *state = user; + int leader_byte = 0; + int intermed_byte = 0; + int cancel_phantom = 1; + + if (leader && leader[0]) { + if (leader[1]) // longer than 1 char + return 0; + + switch (leader[0]) { + case '?': + case '>': + leader_byte = leader[0]; + break; + default: + return 0; + } + } + + if (intermed && intermed[0]) { + if (intermed[1]) // longer than 1 char + return 0; + + switch (intermed[0]) { + case ' ': + case '"': + case '$': + case '\'': + intermed_byte = intermed[0]; + break; + default: + return 0; + } + } + + VTermPos oldpos = state->pos; + + // Some temporaries for later code + int count, val; + int row, col; + VTermRect rect; + int selective; + +#define LBOUND(v, min) \ + if ((v) < (min)) \ + (v) = (min) +#define UBOUND(v, max) \ + if ((v) > (max)) \ + (v) = (max) + +#define LEADER(l, b) ((l << 8) | b) +#define INTERMED(i, b) ((i << 16) | b) + + switch (intermed_byte << 16 | leader_byte << 8 | command) { + case 0x40: // ICH - ECMA-48 8.3.64 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if (state->mode.leftrightmargin) + rect.end_col = SCROLLREGION_RIGHT(state); + else + rect.end_col = THISROWWIDTH(state); + + scroll(state, rect, 0, -count); + + break; + + case 0x41: // CUU - ECMA-48 8.3.22 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x42: // CUD - ECMA-48 8.3.19 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x43: // CUF - ECMA-48 8.3.20 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x44: // CUB - ECMA-48 8.3.18 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x45: // CNL - ECMA-48 8.3.12 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x46: // CPL - ECMA-48 8.3.13 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x47: // CHA - ECMA-48 8.3.9 + val = CSI_ARG_OR(args[0], 1); + state->pos.col = val - 1; + state->at_phantom = 0; + break; + + case 0x48: // CUP - ECMA-48 8.3.21 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row - 1; + state->pos.col = col - 1; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x49: // CHT - ECMA-48 8.3.10 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, +1); + break; + + case 0x4a: // ED - ECMA-48 8.3.39 + case LEADER('?', 0x4a): // DECSED - Selective Erase in Display + selective = (leader_byte == '?'); + switch (CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + rect.end_col = state->cols; + if (rect.end_col > rect.start_col) + erase(state, rect, selective); + + rect.start_row = state->pos.row + 1; + rect.end_row = state->rows; + rect.start_col = 0; + for (int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + if (rect.end_row > rect.start_row) + erase(state, rect, selective); + break; + + case 1: + rect.start_row = 0; + rect.end_row = state->pos.row; + rect.start_col = 0; + rect.end_col = state->cols; + for (int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + if (rect.end_col > rect.start_col) + erase(state, rect, selective); + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.end_col = state->pos.col + 1; + if (rect.end_row > rect.start_row) + erase(state, rect, selective); + break; + + case 2: + rect.start_row = 0; + rect.end_row = state->rows; + rect.start_col = 0; + rect.end_col = state->cols; + for (int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + erase(state, rect, selective); + break; + } + break; + + case 0x4b: // EL - ECMA-48 8.3.41 + case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line + selective = (leader_byte == '?'); + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + + switch (CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_col = state->pos.col; + rect.end_col = THISROWWIDTH(state); + break; + case 1: + rect.start_col = 0; + rect.end_col = state->pos.col + 1; + break; + case 2: + rect.start_col = 0; + rect.end_col = THISROWWIDTH(state); + break; + default: + return 0; + } + + if (rect.end_col > rect.start_col) + erase(state, rect, selective); + + break; + + case 0x4c: // IL - ECMA-48 8.3.67 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x4d: // DL - ECMA-48 8.3.32 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x50: // DCH - ECMA-48 8.3.26 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if (state->mode.leftrightmargin) + rect.end_col = SCROLLREGION_RIGHT(state); + else + rect.end_col = THISROWWIDTH(state); + + scroll(state, rect, 0, count); + + break; + + case 0x53: // SU - ECMA-48 8.3.147 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x54: // SD - ECMA-48 8.3.113 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x58: // ECH - ECMA-48 8.3.38 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + rect.end_col = state->pos.col + count; + UBOUND(rect.end_col, THISROWWIDTH(state)); + + erase(state, rect, 0); + break; + + case 0x5a: // CBT - ECMA-48 8.3.7 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, -1); + break; + + case 0x60: // HPA - ECMA-48 8.3.57 + col = CSI_ARG_OR(args[0], 1); + state->pos.col = col - 1; + state->at_phantom = 0; + break; + + case 0x61: // HPR - ECMA-48 8.3.59 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x62: { // REP - ECMA-48 8.3.103 + const int row_width = THISROWWIDTH(state); + count = CSI_ARG_COUNT(args[0]); + col = state->pos.col + count; + UBOUND(col, row_width); + while (state->pos.col < col) { + putglyph(state, state->combine_chars, state->combine_width, state->pos); + state->pos.col += state->combine_width; + } + if (state->pos.col + state->combine_width >= row_width) { + if (state->mode.autowrap) { + state->at_phantom = 1; + cancel_phantom = 0; + } + } + break; + } + + case 0x63: // DA - ECMA-48 8.3.24 + val = CSI_ARG_OR(args[0], 0); + if (val == 0) + // DEC VT100 response + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); + break; + + case LEADER('>', 0x63): // DEC secondary Device Attributes + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); + break; + + case 0x64: // VPA - ECMA-48 8.3.158 + row = CSI_ARG_OR(args[0], 1); + state->pos.row = row - 1; + if (state->mode.origin) + state->pos.row += state->scrollregion_top; + state->at_phantom = 0; + break; + + case 0x65: // VPR - ECMA-48 8.3.160 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x66: // HVP - ECMA-48 8.3.63 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row - 1; + state->pos.col = col - 1; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x67: // TBC - ECMA-48 8.3.154 + val = CSI_ARG_OR(args[0], 0); + + switch (val) { + case 0: + clear_col_tabstop(state, state->pos.col); + break; + case 3: + case 5: + for (col = 0; col < state->cols; col++) + clear_col_tabstop(state, col); + break; + case 1: + case 2: + case 4: + break; + /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */ + default: + return 0; + } + break; + + case 0x68: // SM - ECMA-48 8.3.125 + if (!CSI_ARG_IS_MISSING(args[0])) + set_mode(state, CSI_ARG(args[0]), 1); + break; + + case LEADER('?', 0x68): // DEC private mode set + if (!CSI_ARG_IS_MISSING(args[0])) + set_dec_mode(state, CSI_ARG(args[0]), 1); + break; + + case 0x6a: // HPB - ECMA-48 8.3.58 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x6b: // VPB - ECMA-48 8.3.159 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x6c: // RM - ECMA-48 8.3.106 + if (!CSI_ARG_IS_MISSING(args[0])) + set_mode(state, CSI_ARG(args[0]), 0); + break; + + case LEADER('?', 0x6c): // DEC private mode reset + if (!CSI_ARG_IS_MISSING(args[0])) + set_dec_mode(state, CSI_ARG(args[0]), 0); + break; + + case 0x6d: // SGR - ECMA-48 8.3.117 + vterm_state_setpen(state, args, argcount); + break; + + case 0x6e: // DSR - ECMA-48 8.3.35 + case LEADER('?', 0x6e): // DECDSR + val = CSI_ARG_OR(args[0], 0); + + { + char *qmark = (leader_byte == '?') ? "?" : ""; + + switch (val) { + case 0: + case 1: + case 2: + case 3: + case 4: + // ignore - these are replies + break; + case 5: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); + break; + case 6: // CPR - cursor position report + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1); + break; + } + } + break; + + + case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset + vterm_state_reset(state, 0); + break; + + case LEADER('?', INTERMED('$', 0x70)): + request_dec_mode(state, CSI_ARG(args[0])); + break; + + case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape + val = CSI_ARG_OR(args[0], 1); + + switch (val) { + case 0: + case 1: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 2: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 3: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 4: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 5: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + case 6: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + } + + break; + + case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute + val = CSI_ARG_OR(args[0], 0); + + switch (val) { + case 0: + case 2: + state->protected_cell = 0; + break; + case 1: + state->protected_cell = 1; + break; + } + + break; + + case 0x72: // DECSTBM - DEC custom + state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_top, 0); + UBOUND(state->scrollregion_top, state->rows); + LBOUND(state->scrollregion_bottom, -1); + if (state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) + state->scrollregion_bottom = -1; + else + UBOUND(state->scrollregion_bottom, state->rows); + + if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + // Invalid + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case 0x73: // DECSLRM - DEC custom + // Always allow setting these margins, just they won't take effect without DECVSSM + state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_left, 0); + UBOUND(state->scrollregion_left, state->cols); + LBOUND(state->scrollregion_right, -1); + if (state->scrollregion_left == 0 && state->scrollregion_right == state->cols) + state->scrollregion_right = -1; + else + UBOUND(state->scrollregion_right, state->cols); + + if (state->scrollregion_right > -1 && + state->scrollregion_right <= state->scrollregion_left) { + // Invalid + state->scrollregion_left = 0; + state->scrollregion_right = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case INTERMED('\'', 0x7D): // DECIC + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, -count); + + break; + + case INTERMED('\'', 0x7E): // DECDC + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, count); + + break; + + default: + if (state->fallbacks && state->fallbacks->csi) + if ((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) + return 1; + + return 0; + } + + if (state->mode.origin) { + LBOUND(state->pos.row, state->scrollregion_top); + UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state) - 1); + LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); + UBOUND(state->pos.col, SCROLLREGION_RIGHT(state) - 1); + } else { + LBOUND(state->pos.row, 0); + UBOUND(state->pos.row, state->rows - 1); + LBOUND(state->pos.col, 0); + UBOUND(state->pos.col, THISROWWIDTH(state) - 1); + } + + updatecursor(state, &oldpos, cancel_phantom); + + return 1; +} + +static int on_osc(const char *command, size_t cmdlen, void *user) { + VTermState *state = user; + + if (cmdlen < 2) + return 0; + + if (strneq(command, "0;", 2)) { + settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); + settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); + return 1; + } else if (strneq(command, "1;", 2)) { + settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); + return 1; + } else if (strneq(command, "2;", 2)) { + settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); + return 1; + } else if (state->fallbacks && state->fallbacks->osc) + if ((*state->fallbacks->osc)(command, cmdlen, state->fbdata)) + return 1; + + return 0; +} + +static void request_status_string(VTermState *state, const char *command, size_t cmdlen) { + VTerm *vt = state->vt; + + if (cmdlen == 1) + switch (command[0]) { + case 'm': // Query SGR + { + long args[20]; + int argc = vterm_state_getpen(state, args, sizeof(args) / sizeof(args[0])); + size_t cur = 0; + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x90" + "1$r" : + ESC_S "P" + "1$r"); // DCS 1$r ... + if (cur >= vt->tmpbuffer_len) + return; + + for (int argi = 0; argi < argc; argi++) { + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, argi == argc - 1 ? "%ld" : CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" : + "%ld;", + CSI_ARG(args[argi])); + if (cur >= vt->tmpbuffer_len) + return; + } + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "m" + "\x9C" : + "m" ESC_S "\\"); // ... m ST + if (cur >= vt->tmpbuffer_len) + return; + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); + } + return; + case 'r': // Query DECSTBM + vterm_push_output_sprintf_dcs(vt, "1$r%d;%dr", state->scrollregion_top + 1, SCROLLREGION_BOTTOM(state)); + return; + case 's': // Query DECSLRM + vterm_push_output_sprintf_dcs(vt, "1$r%d;%ds", SCROLLREGION_LEFT(state) + 1, SCROLLREGION_RIGHT(state)); + return; + } + + if (cmdlen == 2) { + if (strneq(command, " q", 2)) { + int reply; + switch (state->mode.cursor_shape) { + case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; + case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; + case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break; + } + if (state->mode.cursor_blink) + reply--; + vterm_push_output_sprintf_dcs(vt, "1$r%d q", reply); + return; + } else if (strneq(command, "\"q", 2)) { + vterm_push_output_sprintf_dcs(vt, "1$r%d\"q", state->protected_cell ? 1 : 2); + return; + } + } + + vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command); +} + +static int on_dcs(const char *command, size_t cmdlen, void *user) { + VTermState *state = user; + + if (cmdlen >= 2 && strneq(command, "$q", 2)) { + request_status_string(state, command + 2, cmdlen - 2); + return 1; + } else if (state->fallbacks && state->fallbacks->dcs) + if ((*state->fallbacks->dcs)(command, cmdlen, state->fbdata)) + return 1; + + return 0; +} + +static int on_resize(int rows, int cols, void *user) { + VTermState *state = user; + VTermPos oldpos = state->pos; + + if (cols != state->cols) { + unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8); + + /* TODO: This can all be done much more efficiently bytewise */ + int col; + for (col = 0; col < state->cols && col < cols; col++) { + unsigned char mask = 1 << (col & 7); + if (state->tabstops[col >> 3] & mask) + newtabstops[col >> 3] |= mask; + else + newtabstops[col >> 3] &= ~mask; + } + + for (; col < cols; col++) { + unsigned char mask = 1 << (col & 7); + if (col % 8 == 0) + newtabstops[col >> 3] |= mask; + else + newtabstops[col >> 3] &= ~mask; + } + + vterm_allocator_free(state->vt, state->tabstops); + state->tabstops = newtabstops; + } + + if (rows != state->rows) { + VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo)); + + int row; + for (row = 0; row < state->rows && row < rows; row++) { + newlineinfo[row] = state->lineinfo[row]; + } + + for (; row < rows; row++) { + newlineinfo[row] = (VTermLineInfo){ + .doublewidth = 0, + }; + } + + vterm_allocator_free(state->vt, state->lineinfo); + state->lineinfo = newlineinfo; + } + + state->rows = rows; + state->cols = cols; + + if (state->scrollregion_bottom > -1) + UBOUND(state->scrollregion_bottom, state->rows); + if (state->scrollregion_right > -1) + UBOUND(state->scrollregion_right, state->cols); + + VTermPos delta = {0, 0}; + + if (state->callbacks && state->callbacks->resize) + (*state->callbacks->resize)(rows, cols, &delta, state->cbdata); + + if (state->at_phantom && state->pos.col < cols - 1) { + state->at_phantom = 0; + state->pos.col++; + } + + state->pos.row += delta.row; + state->pos.col += delta.col; + + if (state->pos.row >= rows) + state->pos.row = rows - 1; + if (state->pos.col >= cols) + state->pos.col = cols - 1; + + updatecursor(state, &oldpos, 1); + + return 1; +} + +static const VTermParserCallbacks parser_callbacks = { + .text = on_text, + .control = on_control, + .escape = on_escape, + .csi = on_csi, + .osc = on_osc, + .dcs = on_dcs, + .resize = on_resize, +}; + +VTermState *vterm_obtain_state(VTerm *vt) { + if (vt->state) + return vt->state; + + VTermState *state = vterm_state_new(vt); + vt->state = state; + + state->combine_chars_size = 16; + state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0])); + + state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8); + + state->lineinfo = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); + + state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); + if (*state->encoding_utf8.enc->init) + (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); + + vterm_parser_set_callbacks(vt, &parser_callbacks, state); + + return state; +} + +void vterm_state_reset(VTermState *state, int hard) { + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + state->scrollregion_left = 0; + state->scrollregion_right = -1; + + state->mode.keypad = 0; + state->mode.cursor = 0; + state->mode.autowrap = 1; + state->mode.insert = 0; + state->mode.newline = 0; + state->mode.alt_screen = 0; + state->mode.origin = 0; + state->mode.leftrightmargin = 0; + state->mode.bracketpaste = 0; + state->mode.report_focus = 0; + + state->vt->mode.ctrl8bit = 0; + + for (int col = 0; col < state->cols; col++) + if (col % 8 == 0) + set_col_tabstop(state, col); + else + clear_col_tabstop(state, col); + + for (int row = 0; row < state->rows; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + + if (state->callbacks && state->callbacks->initpen) + (*state->callbacks->initpen)(state->cbdata); + + vterm_state_resetpen(state); + + VTermEncoding *default_enc = state->vt->mode.utf8 ? + vterm_lookup_encoding(ENC_UTF8, 'u') : + vterm_lookup_encoding(ENC_SINGLE_94, 'B'); + + for (int i = 0; i < 4; i++) { + state->encoding[i].enc = default_enc; + if (default_enc->init) + (*default_enc->init)(default_enc, state->encoding[i].data); + } + + state->gl_set = 0; + state->gr_set = 1; + state->gsingle_set = 0; + + state->protected_cell = 0; + + // Initialise the props + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + + if (hard) { + state->pos.row = 0; + state->pos.col = 0; + state->at_phantom = 0; + + VTermRect rect = {0, state->rows, 0, state->cols}; + erase(state, rect, 0); + } +} + +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) { + *cursorpos = state->pos; +} + +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) { + if (callbacks) { + state->callbacks = callbacks; + state->cbdata = user; + + if (state->callbacks && state->callbacks->initpen) + (*state->callbacks->initpen)(state->cbdata); + } else { + state->callbacks = NULL; + state->cbdata = NULL; + } +} + +void *vterm_state_get_cbdata(VTermState *state) { + return state->cbdata; +} + +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user) { + if (fallbacks) { + state->fallbacks = fallbacks; + state->fbdata = user; + } else { + state->fallbacks = NULL; + state->fbdata = NULL; + } +} + +void *vterm_state_get_unrecognised_fbdata(VTermState *state) { + return state->fbdata; +} + +int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) { + /* Only store the new value of the property if usercode said it was happy. + * This is especially important for altscreen switching */ + if (state->callbacks && state->callbacks->settermprop) + if (!(*state->callbacks->settermprop)(prop, val, state->cbdata)) + return 0; + + switch (prop) { + case VTERM_PROP_TITLE: + case VTERM_PROP_ICONNAME: + // we don't store these, just transparently pass through + return 1; + case VTERM_PROP_CURSORVISIBLE: + state->mode.cursor_visible = val->boolean; + return 1; + case VTERM_PROP_CURSORBLINK: + state->mode.cursor_blink = val->boolean; + return 1; + case VTERM_PROP_CURSORSHAPE: + state->mode.cursor_shape = val->number; + return 1; + case VTERM_PROP_REVERSE: + state->mode.screen = val->boolean; + return 1; + case VTERM_PROP_ALTSCREEN: + state->mode.alt_screen = val->boolean; + if (state->mode.alt_screen) { + VTermRect rect = { + .start_row = 0, + .start_col = 0, + .end_row = state->rows, + .end_col = state->cols, + }; + erase(state, rect, 0); + } + return 1; + case VTERM_PROP_MOUSE: + state->mouse_flags = 0; + if (val->number) + state->mouse_flags |= MOUSE_WANT_CLICK; + if (val->number == VTERM_PROP_MOUSE_DRAG) + state->mouse_flags |= MOUSE_WANT_DRAG; + if (val->number == VTERM_PROP_MOUSE_MOVE) + state->mouse_flags |= MOUSE_WANT_MOVE; + return 1; + + case VTERM_N_PROPS: + return 0; + } + + return 0; +} + +void vterm_state_focus_in(VTermState *state) { + if (state->mode.report_focus) + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); +} + +void vterm_state_focus_out(VTermState *state) { + if (state->mode.report_focus) + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); +} + +const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) { + return state->lineinfo + row; +} diff --git a/extlib/libvterm/unicode.c b/extlib/libvterm/unicode.c new file mode 100644 index 0000000..e0fcef0 --- /dev/null +++ b/extlib/libvterm/unicode.c @@ -0,0 +1,229 @@ +#include "vterm_internal.h" + +// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c +// With modifications: +// made functions static +// moved 'combining' table to file scope, so other functions can see it +// ################################################################### + +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +struct interval { + int first; + int last; +}; + +/* sorted list of non-overlapping intervals of non-spacing characters */ +/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ +static const struct interval combining[] = { + {0x0300, 0x036F}, {0x0483, 0x0486}, {0x0488, 0x0489}, {0x0591, 0x05BD}, {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, {0x05C4, 0x05C5}, {0x05C7, 0x05C7}, {0x0600, 0x0603}, {0x0610, 0x0615}, {0x064B, 0x065E}, {0x0670, 0x0670}, {0x06D6, 0x06E4}, {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, {0x070F, 0x070F}, {0x0711, 0x0711}, {0x0730, 0x074A}, {0x07A6, 0x07B0}, {0x07EB, 0x07F3}, {0x0901, 0x0902}, {0x093C, 0x093C}, {0x0941, 0x0948}, {0x094D, 0x094D}, {0x0951, 0x0954}, {0x0962, 0x0963}, {0x0981, 0x0981}, {0x09BC, 0x09BC}, {0x09C1, 0x09C4}, {0x09CD, 0x09CD}, {0x09E2, 0x09E3}, {0x0A01, 0x0A02}, {0x0A3C, 0x0A3C}, {0x0A41, 0x0A42}, {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A70, 0x0A71}, {0x0A81, 0x0A82}, {0x0ABC, 0x0ABC}, {0x0AC1, 0x0AC5}, {0x0AC7, 0x0AC8}, {0x0ACD, 0x0ACD}, {0x0AE2, 0x0AE3}, {0x0B01, 0x0B01}, {0x0B3C, 0x0B3C}, {0x0B3F, 0x0B3F}, {0x0B41, 0x0B43}, {0x0B4D, 0x0B4D}, {0x0B56, 0x0B56}, {0x0B82, 0x0B82}, {0x0BC0, 0x0BC0}, {0x0BCD, 0x0BCD}, {0x0C3E, 0x0C40}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56}, {0x0CBC, 0x0CBC}, {0x0CBF, 0x0CBF}, {0x0CC6, 0x0CC6}, {0x0CCC, 0x0CCD}, {0x0CE2, 0x0CE3}, {0x0D41, 0x0D43}, {0x0D4D, 0x0D4D}, {0x0DCA, 0x0DCA}, {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC}, {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35}, {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F71, 0x0F7E}, {0x0F80, 0x0F84}, {0x0F86, 0x0F87}, {0x0F90, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102D, 0x1030}, {0x1032, 0x1032}, {0x1036, 0x1037}, {0x1039, 0x1039}, {0x1058, 0x1059}, {0x1160, 0x11FF}, {0x135F, 0x135F}, {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, {0x17DD, 0x17DD}, {0x180B, 0x180D}, {0x18A9, 0x18A9}, {0x1920, 0x1922}, {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B}, {0x1A17, 0x1A18}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, {0x1B6B, 0x1B73}, {0x1DC0, 0x1DCA}, {0x1DFE, 0x1DFF}, {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2063}, {0x206A, 0x206F}, {0x20D0, 0x20EF}, {0x302A, 0x302F}, {0x3099, 0x309A}, {0xA806, 0xA806}, {0xA80B, 0xA80B}, {0xA825, 0xA826}, {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, {0xFE20, 0xFE23}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169}, {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, {0xE0100, 0xE01EF}}; + + +/* auxiliary function for binary search in interval table */ +static int bisearch(uint32_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that uint32_t characters are encoded + * in ISO 10646. + */ + + +static int mk_wcwidth(uint32_t ucs) { + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + + +static int mk_wcswidth(const uint32_t *pwcs, size_t n) { + int w, width = 0; + + for (; *pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + + +/* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +static int mk_wcwidth_cjk(uint32_t ucs) { + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ + static const struct interval ambiguous[] = { + {0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8}, {0x00AA, 0x00AA}, {0x00AE, 0x00AE}, {0x00B0, 0x00B4}, {0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6}, {0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1}, {0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED}, {0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA}, {0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101}, {0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B}, {0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133}, {0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144}, {0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153}, {0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE}, {0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4}, {0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA}, {0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261}, {0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB}, {0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB}, {0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0391, 0x03A1}, {0x03A3, 0x03A9}, {0x03B1, 0x03C1}, {0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F}, {0x0451, 0x0451}, {0x2010, 0x2010}, {0x2013, 0x2016}, {0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022}, {0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033}, {0x2035, 0x2035}, {0x203B, 0x203B}, {0x203E, 0x203E}, {0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084}, {0x20AC, 0x20AC}, {0x2103, 0x2103}, {0x2105, 0x2105}, {0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116}, {0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B}, {0x2153, 0x2154}, {0x215B, 0x215E}, {0x2160, 0x216B}, {0x2170, 0x2179}, {0x2190, 0x2199}, {0x21B8, 0x21B9}, {0x21D2, 0x21D2}, {0x21D4, 0x21D4}, {0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203}, {0x2207, 0x2208}, {0x220B, 0x220B}, {0x220F, 0x220F}, {0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A}, {0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225}, {0x2227, 0x222C}, {0x222E, 0x222E}, {0x2234, 0x2237}, {0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C}, {0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267}, {0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283}, {0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299}, {0x22A5, 0x22A5}, {0x22BF, 0x22BF}, {0x2312, 0x2312}, {0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573}, {0x2580, 0x258F}, {0x2592, 0x2595}, {0x25A0, 0x25A1}, {0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7}, {0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8}, {0x25CB, 0x25CB}, {0x25CE, 0x25D1}, {0x25E2, 0x25E5}, {0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609}, {0x260E, 0x260F}, {0x2614, 0x2615}, {0x261C, 0x261C}, {0x261E, 0x261E}, {0x2640, 0x2640}, {0x2642, 0x2642}, {0x2660, 0x2661}, {0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D}, {0x266F, 0x266F}, {0x273D, 0x273D}, {0x2776, 0x277F}, {0xE000, 0xF8FF}, {0xFFFD, 0xFFFD}, {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD}}; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return mk_wcwidth(ucs); +} + + +static int mk_wcswidth_cjk(const uint32_t *pwcs, size_t n) { + int w, width = 0; + + for (; *pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth_cjk(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + +// ################################ +// ### The rest added by Paul Evans + +static const struct interval fullwidth[] = { +#include "fullwidth.inc" +}; + +INTERNAL int vterm_unicode_width(uint32_t codepoint) { + if (bisearch(codepoint, fullwidth, sizeof(fullwidth) / sizeof(fullwidth[0]) - 1)) + return 2; + + return mk_wcwidth(codepoint); +} + +INTERNAL int vterm_unicode_is_combining(uint32_t codepoint) { + return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1); +} diff --git a/extlib/libvterm/utf8.h b/extlib/libvterm/utf8.h new file mode 100644 index 0000000..9a336d3 --- /dev/null +++ b/extlib/libvterm/utf8.h @@ -0,0 +1,39 @@ +/* The following functions copied and adapted from libtermkey + * + * http://www.leonerd.org.uk/code/libtermkey/ + */ +static inline unsigned int utf8_seqlen(long codepoint) +{ + if(codepoint < 0x0000080) return 1; + if(codepoint < 0x0000800) return 2; + if(codepoint < 0x0010000) return 3; + if(codepoint < 0x0200000) return 4; + if(codepoint < 0x4000000) return 5; + return 6; +} + +/* Does NOT NUL-terminate the buffer */ +static int fill_utf8(long codepoint, char *str) +{ + int nbytes = utf8_seqlen(codepoint); + + // This is easier done backwards + int b = nbytes; + while(b > 1) { + b--; + str[b] = 0x80 | (codepoint & 0x3f); + codepoint >>= 6; + } + + switch(nbytes) { + case 1: str[0] = (codepoint & 0x7f); break; + case 2: str[0] = 0xc0 | (codepoint & 0x1f); break; + case 3: str[0] = 0xe0 | (codepoint & 0x0f); break; + case 4: str[0] = 0xf0 | (codepoint & 0x07); break; + case 5: str[0] = 0xf8 | (codepoint & 0x03); break; + case 6: str[0] = 0xfc | (codepoint & 0x01); break; + } + + return nbytes; +} +/* end copy */ diff --git a/extlib/libvterm/vterm.c b/extlib/libvterm/vterm.c new file mode 100644 index 0000000..6cbf077 --- /dev/null +++ b/extlib/libvterm/vterm.c @@ -0,0 +1,360 @@ +#include "vterm_internal.h" + +#include +#include +#include +#include + +#include "../../runtime/panic_assert.h" +#include "../../runtime/printf.h" + +/***************** + * API functions * + *****************/ + +/* +static void *default_malloc(size_t size, void *allocdata) { + void *ptr = malloc(size); + if (ptr) + memset(ptr, 0, size); + return ptr; +} + +static void default_free(void *ptr, void *allocdata) { + free(ptr); +} +*/ + +static VTermAllocatorFunctions default_allocator = { + //.malloc = &default_malloc, + //.free = &default_free, +}; + +VTerm *vterm_new(int rows, int cols) { + return vterm_new_with_allocator(rows, cols, &default_allocator, NULL); +} + +VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata) { + /* Need to bootstrap using the allocator function directly */ + VTerm *vt = (*funcs->malloc)(sizeof(VTerm), allocdata); + + vt->allocator = funcs; + vt->allocdata = allocdata; + + vt->rows = rows; + vt->cols = cols; + + vt->parser.state = NORMAL; + + vt->parser.callbacks = NULL; + vt->parser.cbdata = NULL; + + vt->parser.strbuffer_len = 64; + vt->parser.strbuffer_cur = 0; + vt->parser.strbuffer = vterm_allocator_malloc(vt, vt->parser.strbuffer_len); + + vt->outfunc = NULL; + vt->outdata = NULL; + + vt->outbuffer_len = 64; + vt->outbuffer_cur = 0; + vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len); + + vt->tmpbuffer_len = 64; + vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len); + + return vt; +} + +void vterm_free(VTerm *vt) { + if (vt->screen) + vterm_screen_free(vt->screen); + + if (vt->state) + vterm_state_free(vt->state); + + vterm_allocator_free(vt, vt->parser.strbuffer); + vterm_allocator_free(vt, vt->outbuffer); + vterm_allocator_free(vt, vt->tmpbuffer); + + vterm_allocator_free(vt, vt); +} + +INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size) { + return (*vt->allocator->malloc)(size, vt->allocdata); +} + +INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr) { + (*vt->allocator->free)(ptr, vt->allocdata); +} + +void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp) { + if (rowsp) + *rowsp = vt->rows; + if (colsp) + *colsp = vt->cols; +} + +void vterm_set_size(VTerm *vt, int rows, int cols) { + vt->rows = rows; + vt->cols = cols; + + if (vt->parser.callbacks && vt->parser.callbacks->resize) + (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata); +} + +int vterm_get_utf8(const VTerm *vt) { + return vt->mode.utf8; +} + +void vterm_set_utf8(VTerm *vt, int is_utf8) { + vt->mode.utf8 = is_utf8; +} + +void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user) { + vt->outfunc = func; + vt->outdata = user; +} + +INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) { + if (vt->outfunc) { + (vt->outfunc)(bytes, len, vt->outdata); + return; + } + + if (len > vt->outbuffer_len - vt->outbuffer_cur) + return; + + memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len); + vt->outbuffer_cur += len; +} + +INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args) { + size_t len = vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len, format, args); + + vterm_push_output_bytes(vt, vt->tmpbuffer, len); +} + +INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) { + va_list args; + va_start(args, format); + vterm_push_output_vsprintf(vt, format, args); + va_end(args); +} + +INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...) { + size_t cur; + + if (ctrl >= 0x80 && !vt->mode.ctrl8bit) + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, ESC_S "%c", ctrl - 0x40); + else + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, "%c", ctrl); + + if (cur >= vt->tmpbuffer_len) + return; + + va_list args; + va_start(args, fmt); + cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args); + va_end(args); + + if (cur >= vt->tmpbuffer_len) + return; + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +INTERNAL void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...) { + size_t cur = 0; + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x90" : ESC_S "P"); // DCS + + if (cur >= vt->tmpbuffer_len) + return; + + va_list args; + va_start(args, fmt); + cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args); + va_end(args); + + if (cur >= vt->tmpbuffer_len) + return; + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST + + if (cur >= vt->tmpbuffer_len) + return; + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +size_t vterm_output_get_buffer_size(const VTerm *vt) { + return vt->outbuffer_len; +} + +size_t vterm_output_get_buffer_current(const VTerm *vt) { + return vt->outbuffer_cur; +} + +size_t vterm_output_get_buffer_remaining(const VTerm *vt) { + return vt->outbuffer_len - vt->outbuffer_cur; +} + +size_t vterm_output_read(VTerm *vt, char *buffer, size_t len) { + if (len > vt->outbuffer_cur) + len = vt->outbuffer_cur; + + memcpy(buffer, vt->outbuffer, len); + + if (len < vt->outbuffer_cur) + memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len); + + vt->outbuffer_cur -= len; + + return len; +} + +VTermValueType vterm_get_attr_type(VTermAttr attr) { + switch (attr) { + case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT; + case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT; + case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR; + case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR; + + case VTERM_N_ATTRS: return 0; + } + return 0; /* UNREACHABLE */ +} + +VTermValueType vterm_get_prop_type(VTermProp prop) { + switch (prop) { + case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING; + case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING; + case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT; + case VTERM_PROP_MOUSE: return VTERM_VALUETYPE_INT; + + case VTERM_N_PROPS: return 0; + } + return 0; /* UNREACHABLE */ +} + +void vterm_scroll_rect(VTermRect rect, int downward, int rightward, int (*moverect)(VTermRect src, VTermRect dest, void *user), int (*eraserect)(VTermRect rect, int selective, void *user), void *user) { + VTermRect src; + VTermRect dest; + + if (abs(downward) >= rect.end_row - rect.start_row || + abs(rightward) >= rect.end_col - rect.start_col) { + /* Scroll more than area; just erase the lot */ + (*eraserect)(rect, 0, user); + return; + } + + if (rightward >= 0) { + /* rect: [XXX................] + * src: [----------------] + * dest: [----------------] + */ + dest.start_col = rect.start_col; + dest.end_col = rect.end_col - rightward; + src.start_col = rect.start_col + rightward; + src.end_col = rect.end_col; + } else { + /* rect: [................XXX] + * src: [----------------] + * dest: [----------------] + */ + int leftward = -rightward; + dest.start_col = rect.start_col + leftward; + dest.end_col = rect.end_col; + src.start_col = rect.start_col; + src.end_col = rect.end_col - leftward; + } + + if (downward >= 0) { + dest.start_row = rect.start_row; + dest.end_row = rect.end_row - downward; + src.start_row = rect.start_row + downward; + src.end_row = rect.end_row; + } else { + int upward = -downward; + dest.start_row = rect.start_row + upward; + dest.end_row = rect.end_row; + src.start_row = rect.start_row; + src.end_row = rect.end_row - upward; + } + + if (moverect) + (*moverect)(dest, src, user); + + if (downward > 0) + rect.start_row = rect.end_row - downward; + else if (downward < 0) + rect.end_row = rect.start_row - downward; + + if (rightward > 0) + rect.start_col = rect.end_col - rightward; + else if (rightward < 0) + rect.end_col = rect.start_col - rightward; + + (*eraserect)(rect, 0, user); +} + +void vterm_copy_cells(VTermRect dest, VTermRect src, void (*copycell)(VTermPos dest, VTermPos src, void *user), void *user) { + int downward = src.start_row - dest.start_row; + int rightward = src.start_col - dest.start_col; + + int init_row, test_row, init_col, test_col; + int inc_row, inc_col; + + if (downward < 0) { + init_row = dest.end_row - 1; + test_row = dest.start_row - 1; + inc_row = -1; + } else /* downward >= 0 */ { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + + if (rightward < 0) { + init_col = dest.end_col - 1; + test_col = dest.start_col - 1; + inc_col = -1; + } else /* rightward >= 0 */ { + init_col = dest.start_col; + test_col = dest.end_col; + inc_col = +1; + } + + VTermPos pos; + for (pos.row = init_row; pos.row != test_row; pos.row += inc_row) + for (pos.col = init_col; pos.col != test_col; pos.col += inc_col) { + VTermPos srcpos = {pos.row + downward, pos.col + rightward}; + (*copycell)(pos, srcpos, user); + } +} + +void vterm_check_version(int major, int minor) { + if (major != VTERM_VERSION_MAJOR) { + //fprintf(stderr, "libvterm major version mismatch; %d (wants) != %d (library)\n", major, VTERM_VERSION_MAJOR); + Panicf("libvterm major version mismatch; %d (wants) != %d (library)\n", major, VTERM_VERSION_MAJOR); + } + + if (minor > VTERM_VERSION_MINOR) { + //fprintf(stderr, "libvterm minor version mismatch; %d (wants) > %d (library)\n", minor, VTERM_VERSION_MINOR); + Panicf("libvterm minor version mismatch; %d (wants) > %d (library)\n", minor, VTERM_VERSION_MINOR); + } + + // Happy +} diff --git a/extlib/libvterm/vterm.h b/extlib/libvterm/vterm.h new file mode 100644 index 0000000..03c2f77 --- /dev/null +++ b/extlib/libvterm/vterm.h @@ -0,0 +1,533 @@ +#ifndef __VTERM_H__ +#define __VTERM_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "vterm_keycodes.h" + +#define VTERM_VERSION_MAJOR 0 +#define VTERM_VERSION_MINOR 1 + +#define VTERM_CHECK_VERSION \ + vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR) + +typedef struct VTerm VTerm; +typedef struct VTermState VTermState; +typedef struct VTermScreen VTermScreen; + +typedef struct { + int row; + int col; +} VTermPos; + +/* some small utility functions; we can just keep these static here */ + +/* order points by on-screen flow order */ +static inline int vterm_pos_cmp(VTermPos a, VTermPos b) +{ + return (a.row == b.row) ? a.col - b.col : a.row - b.row; +} + +typedef struct { + int start_row; + int end_row; + int start_col; + int end_col; +} VTermRect; + +/* true if the rect contains the point */ +static inline int vterm_rect_contains(VTermRect r, VTermPos p) +{ + return p.row >= r.start_row && p.row < r.end_row && + p.col >= r.start_col && p.col < r.end_col; +} + +/* move a rect */ +static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta) +{ + rect->start_row += row_delta; rect->end_row += row_delta; + rect->start_col += col_delta; rect->end_col += col_delta; +} + +/** + * Bit-field describing the content of the tagged union `VTermColor`. + */ +typedef enum { + /** + * If the lower bit of `type` is not set, the colour is 24-bit RGB. + */ + VTERM_COLOR_RGB = 0x00, + + /** + * The colour is an index into a palette of 256 colours. + */ + VTERM_COLOR_INDEXED = 0x01, + + /** + * Mask that can be used to extract the RGB/Indexed bit. + */ + VTERM_COLOR_TYPE_MASK = 0x01, + + /** + * If set, indicates that this colour should be the default foreground + * color, i.e. there was no SGR request for another colour. When + * rendering this colour it is possible to ignore "idx" and just use a + * colour that is not in the palette. + */ + VTERM_COLOR_DEFAULT_FG = 0x02, + + /** + * If set, indicates that this colour should be the default background + * color, i.e. there was no SGR request for another colour. A common + * option when rendering this colour is to not render a background at + * all, for example by rendering the window transparently at this spot. + */ + VTERM_COLOR_DEFAULT_BG = 0x04, + + /** + * Mask that can be used to extract the default foreground/background bit. + */ + VTERM_COLOR_DEFAULT_MASK = 0x06 +} VTermColorType; + +/** + * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the + * given VTermColor instance is an indexed colour. + */ +#define VTERM_COLOR_IS_INDEXED(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED) + +/** + * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that + * the given VTermColor instance is an rgb colour. + */ +#define VTERM_COLOR_IS_RGB(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB) + +/** + * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating + * that the given VTermColor instance corresponds to the default foreground + * color. + */ +#define VTERM_COLOR_IS_DEFAULT_FG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_FG)) + +/** + * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating + * that the given VTermColor instance corresponds to the default background + * color. + */ +#define VTERM_COLOR_IS_DEFAULT_BG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_BG)) + +/** + * Tagged union storing either an RGB color or an index into a colour palette. + * In order to convert indexed colours to RGB, you may use the + * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb() + * functions which lookup the RGB colour from the palette maintained by a + * VTermState or VTermScreen instance. + */ +typedef union { + /** + * Tag indicating which union member is actually valid. This variable + * coincides with the `type` member of the `rgb` and the `indexed` struct + * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether + * a particular type flag is set. + */ + uint8_t type; + + /** + * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values. + */ + struct { + /** + * Same as the top-level `type` member stored in VTermColor. + */ + uint8_t type; + + /** + * The actual 8-bit red, green, blue colour values. + */ + uint8_t red, green, blue; + } rgb; + + /** + * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into + * the colour palette. + */ + struct { + /** + * Same as the top-level `type` member stored in VTermColor. + */ + uint8_t type; + + /** + * Index into the colour map. + */ + uint8_t idx; + } indexed; +} VTermColor; + +/** + * Constructs a new VTermColor instance representing the given RGB values. + */ +static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, + uint8_t blue) +{ + col->type = VTERM_COLOR_RGB; + col->rgb.red = red; + col->rgb.green = green; + col->rgb.blue = blue; +} + +/** + * Construct a new VTermColor instance representing an indexed color with the + * given index. + */ +static inline void vterm_color_indexed(VTermColor *col, uint8_t idx) +{ + col->type = VTERM_COLOR_INDEXED; + col->indexed.idx = idx; +} + +/** + * Compares two colours. Returns true if the colors are equal, false otherwise. + */ +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b); + +typedef enum { + /* VTERM_VALUETYPE_NONE = 0 */ + VTERM_VALUETYPE_BOOL = 1, + VTERM_VALUETYPE_INT, + VTERM_VALUETYPE_STRING, + VTERM_VALUETYPE_COLOR, + + VTERM_N_VALUETYPES +} VTermValueType; + +typedef union { + int boolean; + int number; + char *string; + VTermColor color; +} VTermValue; + +typedef enum { + /* VTERM_ATTR_NONE = 0 */ + VTERM_ATTR_BOLD = 1, // bool: 1, 22 + VTERM_ATTR_UNDERLINE, // number: 4, 21, 24 + VTERM_ATTR_ITALIC, // bool: 3, 23 + VTERM_ATTR_BLINK, // bool: 5, 25 + VTERM_ATTR_REVERSE, // bool: 7, 27 + VTERM_ATTR_STRIKE, // bool: 9, 29 + VTERM_ATTR_FONT, // number: 10-19 + VTERM_ATTR_FOREGROUND, // color: 30-39 90-97 + VTERM_ATTR_BACKGROUND, // color: 40-49 100-107 + + VTERM_N_ATTRS +} VTermAttr; + +typedef enum { + /* VTERM_PROP_NONE = 0 */ + VTERM_PROP_CURSORVISIBLE = 1, // bool + VTERM_PROP_CURSORBLINK, // bool + VTERM_PROP_ALTSCREEN, // bool + VTERM_PROP_TITLE, // string + VTERM_PROP_ICONNAME, // string + VTERM_PROP_REVERSE, // bool + VTERM_PROP_CURSORSHAPE, // number + VTERM_PROP_MOUSE, // number + + VTERM_N_PROPS +} VTermProp; + +enum { + VTERM_PROP_CURSORSHAPE_BLOCK = 1, + VTERM_PROP_CURSORSHAPE_UNDERLINE, + VTERM_PROP_CURSORSHAPE_BAR_LEFT, + + VTERM_N_PROP_CURSORSHAPES +}; + +enum { + VTERM_PROP_MOUSE_NONE = 0, + VTERM_PROP_MOUSE_CLICK, + VTERM_PROP_MOUSE_DRAG, + VTERM_PROP_MOUSE_MOVE, + + VTERM_N_PROP_MOUSES +}; + +typedef struct { + const uint32_t *chars; + int width; + unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */ + unsigned int dwl:1; /* DECDWL or DECDHL double-width line */ + unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */ +} VTermGlyphInfo; + +typedef struct { + unsigned int doublewidth:1; /* DECDWL or DECDHL line */ + unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */ +} VTermLineInfo; + +typedef struct { + /* libvterm relies on this memory to be zeroed out before it is returned + * by the allocator. */ + void *(*malloc)(size_t size, void *allocdata); + void (*free)(void *ptr, void *allocdata); +} VTermAllocatorFunctions; + +void vterm_check_version(int major, int minor); + +VTerm *vterm_new(int rows, int cols); +VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata); +void vterm_free(VTerm* vt); + +void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp); +void vterm_set_size(VTerm *vt, int rows, int cols); + +int vterm_get_utf8(const VTerm *vt); +void vterm_set_utf8(VTerm *vt, int is_utf8); + +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len); + +/* Setting output callback will override the buffer logic */ +typedef void VTermOutputCallback(const char *s, size_t len, void *user); +void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user); + +/* These buffer functions only work if output callback is NOT set + * These are deprecated and will be removed in a later version */ +size_t vterm_output_get_buffer_size(const VTerm *vt); +size_t vterm_output_get_buffer_current(const VTerm *vt); +size_t vterm_output_get_buffer_remaining(const VTerm *vt); + +/* This too */ +size_t vterm_output_read(VTerm *vt, char *buffer, size_t len); + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod); +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod); + +void vterm_keyboard_start_paste(VTerm *vt); +void vterm_keyboard_end_paste(VTerm *vt); + +void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod); +void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod); + +// ------------ +// Parser layer +// ------------ + +/* Flag to indicate non-final subparameters in a single CSI parameter. + * Consider + * CSI 1;2:3:4;5a + * 1 4 and 5 are final. + * 2 and 3 are non-final and will have this bit set + * + * Don't confuse this with the final byte of the CSI escape; 'a' in this case. + */ +#define CSI_ARG_FLAG_MORE (1U<<31) +#define CSI_ARG_MASK (~(1U<<31)) + +#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE) +#define CSI_ARG(a) ((a) & CSI_ARG_MASK) + +/* Can't use -1 to indicate a missing argument; use this instead */ +#define CSI_ARG_MISSING ((1UL<<31)-1) + +#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING) +#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a)) +#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) + +typedef struct { + int (*text)(const char *bytes, size_t len, void *user); + int (*control)(unsigned char control, void *user); + int (*escape)(const char *bytes, size_t len, void *user); + int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); + int (*osc)(const char *command, size_t cmdlen, void *user); + int (*dcs)(const char *command, size_t cmdlen, void *user); + int (*resize)(int rows, int cols, void *user); +} VTermParserCallbacks; + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user); +void *vterm_parser_get_cbdata(VTerm *vt); + +// ----------- +// State layer +// ----------- + +typedef struct { + int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*erase)(VTermRect rect, int selective, void *user); + int (*initpen)(void *user); + int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, VTermPos *delta, void *user); + int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); +} VTermStateCallbacks; + +VTermState *vterm_obtain_state(VTerm *vt); + +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); +void *vterm_state_get_cbdata(VTermState *state); + +// Only invokes control, csi, osc, dcs +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermParserCallbacks *fallbacks, void *user); +void *vterm_state_get_unrecognised_fbdata(VTermState *state); + +void vterm_state_reset(VTermState *state, int hard); +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); +void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg); +void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col); +void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg); +void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col); +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); +int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val); +void vterm_state_focus_in(VTermState *state); +void vterm_state_focus_out(VTermState *state); +const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row); + +/** + * Makes sure that the given color `col` is indeed an RGB colour. After this + * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other + * flags stored in `col->type` will have been reset. + * + * @param state is the VTermState instance from which the colour palette should + * be extracted. + * @param col is a pointer at the VTermColor instance that should be converted + * to an RGB colour. + */ +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col); + +// ------------ +// Screen layer +// ------------ + +typedef struct { + unsigned int bold : 1; + unsigned int underline : 2; + unsigned int italic : 1; + unsigned int blink : 1; + unsigned int reverse : 1; + unsigned int strike : 1; + unsigned int font : 4; /* 0 to 9 */ + unsigned int dwl : 1; /* On a DECDWL or DECDHL line */ + unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */ +} VTermScreenCellAttrs; + +enum { + VTERM_UNDERLINE_OFF, + VTERM_UNDERLINE_SINGLE, + VTERM_UNDERLINE_DOUBLE, + VTERM_UNDERLINE_CURLY, +}; + +typedef struct { +#define VTERM_MAX_CHARS_PER_CELL 6 + uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; + char width; + VTermScreenCellAttrs attrs; + VTermColor fg, bg; +} VTermScreenCell; + +typedef struct { + int (*damage)(VTermRect rect, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, void *user); + int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); + int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); +} VTermScreenCallbacks; + +VTermScreen *vterm_obtain_screen(VTerm *vt); + +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); +void *vterm_screen_get_cbdata(VTermScreen *screen); + +// Only invokes control, csi, osc, dcs +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermParserCallbacks *fallbacks, void *user); +void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen); + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen); + +typedef enum { + VTERM_DAMAGE_CELL, /* every cell */ + VTERM_DAMAGE_ROW, /* entire rows */ + VTERM_DAMAGE_SCREEN, /* entire screen */ + VTERM_DAMAGE_SCROLL, /* entire screen + scrollrect */ + + VTERM_N_DAMAGES +} VTermDamageSize; + +void vterm_screen_flush_damage(VTermScreen *screen); +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size); + +void vterm_screen_reset(VTermScreen *screen, int hard); + +/* Neither of these functions NUL-terminate the buffer */ +size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect); +size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect); + +typedef enum { + VTERM_ATTR_BOLD_MASK = 1 << 0, + VTERM_ATTR_UNDERLINE_MASK = 1 << 1, + VTERM_ATTR_ITALIC_MASK = 1 << 2, + VTERM_ATTR_BLINK_MASK = 1 << 3, + VTERM_ATTR_REVERSE_MASK = 1 << 4, + VTERM_ATTR_STRIKE_MASK = 1 << 5, + VTERM_ATTR_FONT_MASK = 1 << 6, + VTERM_ATTR_FOREGROUND_MASK = 1 << 7, + VTERM_ATTR_BACKGROUND_MASK = 1 << 8, + + VTERM_ALL_ATTRS_MASK = (1 << 9) - 1 +} VTermAttrMask; + +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); + +int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell); + +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); + +/** + * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state` + * instance. + */ +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col); + +// --------- +// Utilities +// --------- + +VTermValueType vterm_get_attr_type(VTermAttr attr); +VTermValueType vterm_get_prop_type(VTermProp prop); + +void vterm_scroll_rect(VTermRect rect, + int downward, + int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, int selective, void *user), + void *user); + +void vterm_copy_cells(VTermRect dest, + VTermRect src, + void (*copycell)(VTermPos dest, VTermPos src, void *user), + void *user); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/extlib/libvterm/vterm_internal.h b/extlib/libvterm/vterm_internal.h new file mode 100644 index 0000000..b1e6e86 --- /dev/null +++ b/extlib/libvterm/vterm_internal.h @@ -0,0 +1,252 @@ +#ifndef __VTERM_INTERNAL_H__ +#define __VTERM_INTERNAL_H__ + +#include "vterm.h" + +#include + +#if defined(__GNUC__) +# define INTERNAL __attribute__((visibility("internal"))) +#else +# define INTERNAL +#endif + +#ifdef DEBUG +# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) +#else +# define DEBUG_LOG(...) +#endif + +#define ESC_S "\x1b" + +#define INTERMED_MAX 16 + +#define CSI_ARGS_MAX 16 +#define CSI_LEADER_MAX 16 + +typedef struct VTermEncoding VTermEncoding; + +typedef struct { + VTermEncoding *enc; + + // This size should be increased if required by other stateful encodings + char data[4*sizeof(uint32_t)]; +} VTermEncodingInstance; + +struct VTermPen +{ + VTermColor fg; + VTermColor bg; + unsigned int bold:1; + unsigned int underline:2; + unsigned int italic:1; + unsigned int blink:1; + unsigned int reverse:1; + unsigned int strike:1; + unsigned int font:4; /* To store 0-9 */ +}; + +struct VTermState +{ + VTerm *vt; + + const VTermStateCallbacks *callbacks; + void *cbdata; + + const VTermParserCallbacks *fallbacks; + void *fbdata; + + int rows; + int cols; + + /* Current cursor position */ + VTermPos pos; + + int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */ + + int scrollregion_top; + int scrollregion_bottom; /* -1 means unbounded */ +#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows) + int scrollregion_left; +#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0) + int scrollregion_right; /* -1 means unbounded */ +#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols) + + /* Bitvector of tab stops */ + unsigned char *tabstops; + + VTermLineInfo *lineinfo; +#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols) +#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row) + + /* Mouse state */ + int mouse_col, mouse_row; + int mouse_buttons; + int mouse_flags; +#define MOUSE_WANT_CLICK 0x01 +#define MOUSE_WANT_DRAG 0x02 +#define MOUSE_WANT_MOVE 0x04 + + enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol; + + /* Last glyph output, for Unicode recombining purposes */ + uint32_t *combine_chars; + size_t combine_chars_size; // Number of ELEMENTS in the above + int combine_width; // The width of the glyph above + VTermPos combine_pos; // Position before movement + + struct { + unsigned int keypad:1; + unsigned int cursor:1; + unsigned int autowrap:1; + unsigned int insert:1; + unsigned int newline:1; + unsigned int cursor_visible:1; + unsigned int cursor_blink:1; + unsigned int cursor_shape:2; + unsigned int alt_screen:1; + unsigned int origin:1; + unsigned int screen:1; + unsigned int leftrightmargin:1; + unsigned int bracketpaste:1; + unsigned int report_focus:1; + } mode; + + VTermEncodingInstance encoding[4], encoding_utf8; + int gl_set, gr_set, gsingle_set; + + struct VTermPen pen; + + VTermColor default_fg; + VTermColor default_bg; + VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only + + int bold_is_highbright; + + unsigned int protected_cell : 1; + + /* Saved state under DEC mode 1048/1049 */ + struct { + VTermPos pos; + struct VTermPen pen; + + struct { + unsigned int cursor_visible:1; + unsigned int cursor_blink:1; + unsigned int cursor_shape:2; + } mode; + } saved; +}; + +typedef enum { + VTERM_PARSER_OSC, + VTERM_PARSER_DCS, + + VTERM_N_PARSER_TYPES +} VTermParserStringType; + +struct VTerm +{ + VTermAllocatorFunctions *allocator; + void *allocdata; + + int rows; + int cols; + + struct { + unsigned int utf8:1; + unsigned int ctrl8bit:1; + } mode; + + struct { + enum VTermParserState { + NORMAL, + CSI_LEADER, + CSI_ARGS, + CSI_INTERMED, + ESC, + /* below here are the "string states" */ + STRING, + ESC_IN_STRING, + } state; + + int intermedlen; + char intermed[INTERMED_MAX]; + + int csi_leaderlen; + char csi_leader[CSI_LEADER_MAX]; + + int csi_argi; + long csi_args[CSI_ARGS_MAX]; + + const VTermParserCallbacks *callbacks; + void *cbdata; + + VTermParserStringType stringtype; + char *strbuffer; + size_t strbuffer_len; + size_t strbuffer_cur; + } parser; + + /* len == malloc()ed size; cur == number of valid bytes */ + + VTermOutputCallback *outfunc; + void *outdata; + + char *outbuffer; + size_t outbuffer_len; + size_t outbuffer_cur; + + char *tmpbuffer; + size_t tmpbuffer_len; + + VTermState *state; + VTermScreen *screen; +}; + +struct VTermEncoding { + void (*init) (VTermEncoding *enc, void *data); + void (*decode)(VTermEncoding *enc, void *data, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t len); +}; + +typedef enum { + ENC_UTF8, + ENC_SINGLE_94 +} VTermEncodingType; + +void *vterm_allocator_malloc(VTerm *vt, size_t size); +void vterm_allocator_free(VTerm *vt, void *ptr); + +void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len); +void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args); +void vterm_push_output_sprintf(VTerm *vt, const char *format, ...); +void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...); +void vterm_push_output_sprintf_dcs(VTerm *vt, const char *fmt, ...); + +void vterm_state_free(VTermState *state); + +void vterm_state_newpen(VTermState *state); +void vterm_state_resetpen(VTermState *state); +void vterm_state_setpen(VTermState *state, const long args[], int argcount); +int vterm_state_getpen(VTermState *state, long args[], int argcount); +void vterm_state_savepen(VTermState *state, int save); + +enum { + C1_SS3 = 0x8f, + C1_DCS = 0x90, + C1_CSI = 0x9b, + C1_ST = 0x9c, +}; + +void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...); + +void vterm_screen_free(VTermScreen *screen); + +VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation); + +int vterm_unicode_width(uint32_t codepoint); +int vterm_unicode_is_combining(uint32_t codepoint); + +#endif diff --git a/extlib/libvterm/vterm_keycodes.h b/extlib/libvterm/vterm_keycodes.h new file mode 100644 index 0000000..661759f --- /dev/null +++ b/extlib/libvterm/vterm_keycodes.h @@ -0,0 +1,61 @@ +#ifndef __VTERM_INPUT_H__ +#define __VTERM_INPUT_H__ + +typedef enum { + VTERM_MOD_NONE = 0x00, + VTERM_MOD_SHIFT = 0x01, + VTERM_MOD_ALT = 0x02, + VTERM_MOD_CTRL = 0x04, + + VTERM_ALL_MODS_MASK = 0x07 +} VTermModifier; + +typedef enum { + VTERM_KEY_NONE, + + VTERM_KEY_ENTER, + VTERM_KEY_TAB, + VTERM_KEY_BACKSPACE, + VTERM_KEY_ESCAPE, + + VTERM_KEY_UP, + VTERM_KEY_DOWN, + VTERM_KEY_LEFT, + VTERM_KEY_RIGHT, + + VTERM_KEY_INS, + VTERM_KEY_DEL, + VTERM_KEY_HOME, + VTERM_KEY_END, + VTERM_KEY_PAGEUP, + VTERM_KEY_PAGEDOWN, + + VTERM_KEY_FUNCTION_0 = 256, + VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255, + + VTERM_KEY_KP_0, + VTERM_KEY_KP_1, + VTERM_KEY_KP_2, + VTERM_KEY_KP_3, + VTERM_KEY_KP_4, + VTERM_KEY_KP_5, + VTERM_KEY_KP_6, + VTERM_KEY_KP_7, + VTERM_KEY_KP_8, + VTERM_KEY_KP_9, + VTERM_KEY_KP_MULT, + VTERM_KEY_KP_PLUS, + VTERM_KEY_KP_COMMA, + VTERM_KEY_KP_MINUS, + VTERM_KEY_KP_PERIOD, + VTERM_KEY_KP_DIVIDE, + VTERM_KEY_KP_ENTER, + VTERM_KEY_KP_EQUAL, + + VTERM_KEY_MAX, // Must be last + VTERM_N_KEYS = VTERM_KEY_MAX +} VTermKey; + +#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n)) + +#endif diff --git a/graphics/graphics.c b/graphics/graphics.c new file mode 100644 index 0000000..19b3559 --- /dev/null +++ b/graphics/graphics.c @@ -0,0 +1,284 @@ + + +#include "graphics.h" +#include "unifont.h" +#include "../runtime/stdio.h" +#include + +#include +#include +#include "../runtime/memcpy.h" + +const HelosGraphics_Color + HelosGraphics_Color_Black = {0x00, 0x00, 0x00, 0xff}, + HelosGraphics_Color_White = {0xff, 0xff, 0xff, 0xff}, + HelosGraphics_Color_Red = {0x00, 0x00, 0xff, 0xff}, + HelosGraphics_Color_Green = {0x00, 0xff, 0x00, 0xff}, + HelosGraphics_Color_Blue = {0xff, 0x00, 0x00, 0xff}, + HelosGraphics_Color_Cyan = {0xff, 0xff, 0x00, 0xff}, + HelosGraphics_Color_Magenta = {0xff, 0x00, 0xff, 0xff}, + HelosGraphics_Color_Yellow = {0x00, 0xff, 0xff, 0xff}; + + +static EFI_GUID gopID = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; +static EFI_GRAPHICS_OUTPUT_PROTOCOL *gop; + +static EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info; +static UINTN sizeofInfo, nModes, nativeMode; +static HelosGraphics_Mode modes[128]; + +void *graphics_DeviceFramebuffer; + +void * graphics_Framebuffer; +uint64_t graphics_FramebufferSize; + +uint32_t graphics_Doublebuffer[2048 * 1024]; + + +void graphics_Init() { + assert(sizeof(HelosGraphics_Color) == sizeof(uint32_t) && "HelosGraphics_Color not packed to be 32-bit(4 bytes)"); + + EFI_STATUS status = efiBootServices->LocateProtocol(&gopID, NULL, (void **)&gop); + if (EFI_ERROR(status)) { + io_Printf("graphics_Init: Error locating GOP\r\n"); + return; + } + + + status = gop->QueryMode(gop, gop->Mode == NULL ? 0 : gop->Mode->Mode, &sizeofInfo, &info); + if (status == EFI_NOT_STARTED) { + status = gop->SetMode(gop, 0); + } + if (EFI_ERROR(status)) { + io_Printf("graphics_Init: Error getting native modes\r\n"); + return; + } else { + nativeMode = gop->Mode->Mode; + nModes = gop->Mode->MaxMode; + } + + for (int i = 0; i < nModes; i++) { + status = gop->QueryMode(gop, i, &sizeofInfo, &info); + modes[i].Width = info->HorizontalResolution; + modes[i].Height = info->VerticalResolution; + modes[i].PixelsPerLine = info->PixelsPerScanLine; + modes[i].PixelFormat = info->PixelFormat; + + if (modes[i].Width == HELOS_GRAPHICS_TARGET_MODE_WIDTH && modes[i].Height == HELOS_GRAPHICS_TARGET_MODE_HEIGHT) + nativeMode = i; + } + + // set the new mode + status = gop->SetMode(gop, nativeMode); + if (EFI_ERROR(status)) { + io_Printf("graphics_Init: Unable to set mode %d\r\n", nativeMode); + return; + } + + io_Printf( + "graphics_Init: Framebuffer:\r\n addr %08x len %d, size %dx%d, ppl %d\r\n", + graphics_DeviceFramebuffer = (void *)gop->Mode->FrameBufferBase, + graphics_FramebufferSize = gop->Mode->FrameBufferSize, + gop->Mode->Info->HorizontalResolution, + gop->Mode->Info->VerticalResolution, + gop->Mode->Info->PixelsPerScanLine); + + // warn the user if the framebuffer is not RGB + io_Printf("graphics_Init: Framebuffer format: "); +#define CASE(v) \ + break; \ + case v: io_Printf(#v "\r\n"); + switch (gop->Mode->Info->PixelFormat) { + CASE(PixelRedGreenBlueReserved8BitPerColor) + graphics_SetPixel = graphics_SetPixel_RGB; + CASE(PixelBlueGreenRedReserved8BitPerColor) + graphics_SetPixel = graphics_SetPixel_BGR; + CASE(PixelBitMask) + CASE(PixelBltOnly) + CASE(PixelFormatMax) + } +#undef CASE + + graphics_Framebuffer = (void *)graphics_Doublebuffer; + graphics_CursorX = graphics_CursorY = 0; +} + + +graphics_SetPixel_Type *graphics_SetPixel; + +void graphics_SetPixel_RGB(int posX, int posY, const HelosGraphics_Color *color) { + struct { + uint8_t R, G, B; + uint8_t A; + } colorRGB = {color->R, color->G, color->B, 0}; + + *((uint32_t *)(graphics_Framebuffer + modes[nativeMode].PixelsPerLine * 4 * posY + 4 * posX)) = (*((uint32_t *)&colorRGB)); +} + +void graphics_SetPixel_BGR(int posX, int posY, const HelosGraphics_Color *color) { + *((uint32_t *)(graphics_Framebuffer + modes[nativeMode].PixelsPerLine * 4 * posY + 4 * posX)) = (*((uint32_t *)color)) & 0x00ffffffu; +} + + +void graphics_GetSize(int *sizeX, int *sizeY, int *bitsPerPixel) { + *sizeX = modes[nativeMode].Width; + *sizeY = modes[nativeMode].Height; + *bitsPerPixel = 24; +} + +void graphics_ClearBuffer(const HelosGraphics_Color *color) { + uint32_t data; + + if (*((uint32_t *)color) == *((uint32_t *)&HelosGraphics_Color_Black)) { + memset(graphics_Framebuffer, 0, graphics_FramebufferSize); + return; + } + + if (modes[nativeMode].PixelFormat == PixelRedGreenBlueReserved8BitPerColor) { + struct { + uint8_t R, G, B; + uint8_t A; + } colorRGB = {color->R, color->G, color->B, 0}; + data = (*(uint32_t *)&colorRGB) & 0x00ffffffu; + } else if (modes[nativeMode].PixelFormat == PixelBlueGreenRedReserved8BitPerColor) { + data = (*(uint32_t *)color) & 0x00ffffffu; + } + + uint32_t *buffer = graphics_Framebuffer, *end = graphics_Framebuffer + graphics_FramebufferSize / sizeof(uint32_t); + while (buffer != end) { + *buffer = data; + buffer++; + } +} + +void graphics_SwapBuffer() { + memcpy(graphics_DeviceFramebuffer, graphics_Framebuffer, graphics_FramebufferSize); +} + +void graphics_FillPixel(int startX, int startY, int endX, int endY, const HelosGraphics_Color *color) { + // TODO Optimize this! This is too sloooow + if (gop->Mode->Info->PixelFormat == PixelBlueGreenRedReserved8BitPerColor) + for (int i = startX; i < endX; i++) + for (int j = startY; j < endY; j++) + *((uint32_t *)(graphics_Framebuffer + modes[nativeMode].PixelsPerLine * 4 * j + 4 * i)) = (*((uint32_t *)color)) & 0x00ffffffu; + else if (gop->Mode->Info->PixelFormat == PixelRedGreenBlueReserved8BitPerColor) + for (int i = startX; i < endX; i++) + for (int j = startY; j < endY; j++) { + struct { + uint8_t B, G, R; + uint8_t A; + } colorRGB = {color->B, color->G, color->R, 0}; + + *((uint32_t *)(graphics_Framebuffer + modes[nativeMode].PixelsPerLine * 4 * j + 4 * i)) = (*((uint32_t *)&colorRGB)); + } +} + +int graphics_CursorX, graphics_CursorY; + +void graphics_Scroll(int scrollY) { + memmove( + graphics_Doublebuffer, + graphics_Doublebuffer + modes[nativeMode].PixelsPerLine * scrollY, + sizeof(uint32_t) * (modes[nativeMode].PixelsPerLine * (modes[nativeMode].Height - scrollY))); + // TODO proper memset instead of this sloooow FillPixel + /*memset( + graphics_Doublebuffer + modes[nativeMode].PixelsPerLine * (modes[nativeMode].Height - scrollY), + 0, + sizeof(uint32_t) * modes[nativeMode].PixelsPerLine * (scrollY));*/ + graphics_FillPixel(0, modes[nativeMode].Height - scrollY, modes[nativeMode].Width, modes[nativeMode].Height, &HelosGraphics_Color_Black); +} + +void graphics_ElementSize(int sizeX, int sizeY) { + if (graphics_CursorX + sizeX >= modes[nativeMode].Width) { // line breaking required + graphics_CursorY += sizeY; + graphics_CursorX = 0; + } + if (graphics_CursorY + sizeY >= modes[nativeMode].Height) { // scrolling required + graphics_Scroll(sizeY + graphics_CursorY - modes[nativeMode].Height); + graphics_CursorY = modes[nativeMode].Height - sizeY; + } +} + +void graphics_Newline(int advanceY) { + graphics_CursorY += advanceY; + graphics_CursorX = 0; +} + + +void console_WriteChar(const HelosGraphics_Color *color, uint32_t c) { + int width; + switch (c) { + case '\n': + graphics_Newline(UNIFONT_CHAR_HEIGHT); + break; + case '\r': + graphics_CursorX = 0; + break; + default: + width = unifont_IsCharDoublewidth(c) ? UNIFONT_CHAR_WIDTH * 2 : UNIFONT_CHAR_WIDTH; + graphics_ElementSize(width, UNIFONT_CHAR_HEIGHT); + graphics_FillPixel(graphics_CursorX, graphics_CursorY, graphics_CursorX + width, graphics_CursorY + UNIFONT_CHAR_HEIGHT, &HelosGraphics_Color_Black); + unifont_DrawChar(graphics_CursorX, graphics_CursorY, color, c); + graphics_CursorX += width; + } +} + +void console_Write(const HelosGraphics_Color *color, const uint32_t *str, int len) { + bool wantSwap = false; + if (len == 0) { + while (*str != 0) { + console_WriteChar(color, *str); + if (*str == '\n') + wantSwap = true; + str++; + } + } else { + for (int i = 0; i < len; i++) { + console_WriteChar(color, str[i]); + if (str[i] == '\n') + wantSwap = true; + } + } + if (wantSwap) + graphics_SwapBuffer(); +} + +void console_WriteUTF16(const HelosGraphics_Color *color, const uint16_t *str, int len) { + bool wantSwap = false; + if (len == 0) { + while (*str != 0) { + console_WriteChar(color, *str); + if (*str == '\n') + wantSwap = true; + str++; + } + } else { + for (int i = 0; i < len; i++) { + console_WriteChar(color, str[i]); + if (str[i] == '\n') + wantSwap = true; + } + } + if (wantSwap) + graphics_SwapBuffer(); +} + +void console_WriteASCII(const HelosGraphics_Color *color, const char *str, int len) { + bool wantSwap = false; + if (len == 0) { + while (*str != 0) { + console_WriteChar(color, *str); + if (*str == '\n') + wantSwap = true; + str++; + } + } else { + for (int i = 0; i < len; i++) { + console_WriteChar(color, str[i]); + if (str[i] == '\n') + wantSwap = true; + } + } + if (wantSwap) + graphics_SwapBuffer(); +} diff --git a/graphics/graphics.h b/graphics/graphics.h new file mode 100644 index 0000000..b2cf83f --- /dev/null +++ b/graphics/graphics.h @@ -0,0 +1,86 @@ +#pragma once + +#include "../main.h" +#include "efiprot.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +// This defines a default target display mode for graphics_Init(). +#define HELOS_GRAPHICS_TARGET_MODE_WIDTH 1600 +#define HELOS_GRAPHICS_TARGET_MODE_HEIGHT 900 + +// HelosGraphics_Color is in ARGB little-endian packed format +// (B,G,R,A in byte order) +typedef struct { + uint8_t B, G, R; + uint8_t A; +} PACKED HelosGraphics_Color; + +extern const HelosGraphics_Color + HelosGraphics_Color_Black, + HelosGraphics_Color_White, + HelosGraphics_Color_Red, + HelosGraphics_Color_Green, + HelosGraphics_Color_Blue, + HelosGraphics_Color_Cyan, + HelosGraphics_Color_Magenta, + HelosGraphics_Color_Yellow; + +typedef struct { + int Width, Height; + int PixelsPerLine; + + EFI_GRAPHICS_PIXEL_FORMAT PixelFormat; +} HelosGraphics_Mode; + + +extern void * graphics_DeviceFramebuffer; // this is the framebuffer directly for the device via memory mapping. +extern void * graphics_Framebuffer; // this is the double-buffered framebuffer (back buffer) +extern uint64_t graphics_FramebufferSize; + + +// Init() must be called prior to ExitBootServices() +void graphics_Init(); + + +// bitsPerPixel does not count the alpha bits, i.e., is 24 on most modern monitors +void graphics_GetSize(int *sizeX, int *sizeY, int *bitsPerPixel); +void graphics_ClearBuffer(const HelosGraphics_Color *color); +void graphics_SwapBuffer(); + + +// graphics_SetPixel is set by Init() to match one of SetPixel_RGB/BGR according to the framebuffer format. +typedef void(graphics_SetPixel_Type)(int posX, int posY, const HelosGraphics_Color *color); +extern graphics_SetPixel_Type *graphics_SetPixel; + +// graphics_SetPixel_RGB/BGR writes the given pixel to the framebuffer in RGB/BGR format. +void graphics_SetPixel_RGB(int posX, int posY, const HelosGraphics_Color *color); +void graphics_SetPixel_BGR(int posX, int posY, const HelosGraphics_Color *color); + + +void graphics_FillPixel(int startX, int startY, int endX, int endY, const HelosGraphics_Color *color); + + +extern int graphics_CursorX, graphics_CursorY; + +// graphics_Scroll scrolls the display vertically by scrollY pixels. +void graphics_Scroll(int scrollY); +// graphics_ElementSize handles size of an graphics element: scrolling, line breaking, etc. +// +// It does not change CursorX/Y, however. +void graphics_ElementSize(int sizeX, int sizeY); +void graphics_Newline(int advanceY); + + +void console_WriteChar(const HelosGraphics_Color *color, uint32_t c); +void console_Write(const HelosGraphics_Color *color, const uint32_t *str, int len); +void console_WriteUTF16(const HelosGraphics_Color *color, const uint16_t *str, int len); +void console_WriteASCII(const HelosGraphics_Color *color, const char *str, int len); + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/graphics/test_unifont.c b/graphics/test_unifont.c new file mode 100644 index 0000000..68322d9 --- /dev/null +++ b/graphics/test_unifont.c @@ -0,0 +1,18 @@ +#include + +#include "Unifont.h" + +int main() { + printf("Data at 0x%X\n\n", unifont_Data); + + for (int i = 0; i < 64; i++) { + printf("%02d(%02X):", i, i); + for (int j = 0; j < 32; j++) { + printf("%02X", (unsigned int)unifont_Data[i * 32 + j]); + } + printf("\n"); + } + + + return 0; +} diff --git a/graphics/unifont.c b/graphics/unifont.c new file mode 100644 index 0000000..7910a1e --- /dev/null +++ b/graphics/unifont.c @@ -0,0 +1,43 @@ + +#include "unifont.h" +#include "graphics.h" +#include + +size_t strlen(const char *); + +void unifont_DrawChar(int posX, int posY, const HelosGraphics_Color *color, uint32_t codepoint) { + const unsigned char *data = unifont_Data + codepoint * UNIFONT_CHAR_WIDTH * UNIFONT_CHAR_HEIGHT * 2 / 8; + bool wide = unifont_IsCharDoublewidth(codepoint); + + int charWidth = UNIFONT_CHAR_WIDTH * (wide ? 2 : 1); + + for (int x = 0; x < charWidth; x++) + for (int y = 0; y < UNIFONT_CHAR_HEIGHT; y++) { + int pos = y * charWidth + x; + if (data[pos / 8] & (1u << (7 - pos % 8))) + graphics_SetPixel(posX + x, posY + y, color); + } +} + +void unifont_DrawString(int posX, int posY, const HelosGraphics_Color *color, const uint32_t *codepoints, int count) { + for (const uint32_t *end = codepoints + count; codepoints != end; codepoints++) { + unifont_DrawChar(posX, posY, color, *codepoints); + posX += UNIFONT_CHAR_WIDTH * (unifont_IsCharDoublewidth(*codepoints) ? 2 : 1); + } +} +void unifont_DrawStringUTF16(int posX, int posY, const HelosGraphics_Color *color, const uint16_t *codepoints, int count) { + for (const uint16_t *end = codepoints + count; codepoints != end; codepoints++) { + unifont_DrawChar(posX, posY, color, *codepoints); + posX += UNIFONT_CHAR_WIDTH * (unifont_IsCharDoublewidth(*codepoints) ? 2 : 1); + } +} +void unifont_DrawStringASCII(int posX, int posY, const HelosGraphics_Color *color, const char *codepoints, int count) { + if (count == 0) { + count = strlen(codepoints); + } + for (const char *end = codepoints + count; codepoints != end; codepoints++) { + unifont_DrawChar(posX, posY, color, *codepoints); + //posX += UNIFONT_CHAR_WIDTH * (unifont_IsCharDoublewidth(*codepoints) ? 2 : 1); + posX += UNIFONT_CHAR_WIDTH; // visible ASCII chars are all single-width + } +} diff --git a/graphics/unifont.h b/graphics/unifont.h new file mode 100644 index 0000000..b164151 --- /dev/null +++ b/graphics/unifont.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../main.h" +#include "graphics.h" + +#include + + +#define UNIFONT_MAX_CHAR (0xffff) +#define UNIFONT_CHAR_COUNT (0xffff + 1) + +#define UNIFONT_CHAR_WIDTH 8 +#define UNIFONT_CHAR_HEIGHT 16 + +extern const unsigned char unifont_Data[], unifont_Width[]; +extern const unsigned char unifont_Data_End[], unifont_Width_End[]; // Past-the-end pointers for the data files + +static inline bool unifont_IsCharDoublewidth(uint32_t codepoint) { + const unsigned char *ptr = unifont_Width + codepoint / 8; + if (ptr < unifont_Width_End) + return (*ptr) & (1u << (7 - codepoint % 8)); + else + return false; +} + +void unifont_DrawChar(int posX, int posY, const HelosGraphics_Color *color, uint32_t codepoint); +void unifont_DrawString(int posX, int posY, const HelosGraphics_Color *color, const uint32_t *codepoints, int count); +void unifont_DrawStringUTF16(int posX, int posY, const HelosGraphics_Color *color, const uint16_t *codepoints, int count); +void unifont_DrawStringASCII(int posX, int posY, const HelosGraphics_Color *color, const char *codepoints, int count); diff --git a/graphics/xcursor/xcursor.h b/graphics/xcursor/xcursor.h new file mode 100644 index 0000000..302a983 --- /dev/null +++ b/graphics/xcursor/xcursor.h @@ -0,0 +1,71 @@ +#pragma once + +#include "../../main.h" +#include "../graphics.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// XCursor file format - see man page xcursor(3) +// https://www.x.org/releases/X11R7.7/doc/man/man3/Xcursor.3.xhtml + +// Header +typedef struct { + char magic[4]; // Magic string "Xcur" + uint32_t headerSize; // Size of the header (16) + uint32_t version; // File version number + uint32_t numTOC; // Number of entries in the Table of Contents +} PACKED xcursor_Header; + +// Table of Content Entry +typedef struct { + uint32_t type; // Entry chunk type (0xfffe0001 = Comment, 0xfffd0002 = Image) + uint32_t subtype; // Type specific Subtype, size(width=height) for images + uint32_t offset; // Absolute byte position in the file +} PACKED xcursor_TOCEntry; + + +#define XCURSOR_CHUNKTYPE_COMMENT 0xfffe0001u +#define XCURSOR_CHUNKTYPE_IMAGE 0xfffd0002u + +// Common parts in different types of Chunk Headers +typedef struct { + uint32_t headerSize; // Size of the header + uint32_t type; // Type of the Chunk, matches the Entry Type in the TOC + uint32_t subtype; // Type specific subtype + uint32_t version; // Version number of the chunk type +} PACKED xcursor_ChunkHeader; + +#define XCURSOR_COMMENT_SUBTYPE_COPYRIGHT 0x00000001u +#define XCURSOR_COMMENT_SUBTYPE_LICENSE 0x00000002u +#define XCURSOR_COMMENT_SUBTYPE_OTHER 0x00000003u + +// Chunk Header for type Comment +typedef struct { + uint32_t headerSize; // Size of the header + uint32_t type; // Type of the Chunk, matches the Entry Type in the TOC + uint32_t subtype; // Type specific subtype, Copyright, License or Other + uint32_t version; // Version number of the chunk type, =1 + + uint32_t length; // Length in bytes of the UTF-8 string + char string[1]; // The UTF-8 string, spanning the rest of the chunk +} PACKED xcursor_ChunkHeader_Comment; + +typedef struct { + uint32_t headerSize; // Size of the header + uint32_t type; // Type of the Chunk, matches the Entry Type in the TOC + uint32_t subtype; // Type specific subtype, Copyright, License or Other + uint32_t version; // Version number of the chunk type, =1 + + uint32_t width, height; // Width/Height, <=0x7fff + uint32_t xhot, yhot; // X/Y hotpoint, <=Width/Height + uint32_t delay; // Delay between animation frames in milliseconds + HelosGraphics_Color pixels[1]; // Packed ARGB little-endian format pixels, with A at the highest byte (BGRA in byte order) +} PACKED xcursor_ChunkHeader_Image; + + +#ifdef __cplusplus +} +#endif diff --git a/interrupt/handler.asm.S b/interrupt/handler.asm.S new file mode 100644 index 0000000..b9312c5 --- /dev/null +++ b/interrupt/handler.asm.S @@ -0,0 +1,194 @@ +format elf64 + +; sysvx64call void interrupt_Handler(a, b, c, d, e, f) +extrn interrupt_Handler +; sysvx64call void interrupt_Handler128(a, b, c, d, e, f) +; Input: rax(syscall opcode) +extrn interrupt_Handler128 + +extrn io_WriteConsoleASCII + +public interrupt_Int0 +public interrupt_Int1 +public interrupt_Int2 +public interrupt_Int3 +public interrupt_Int4 +public interrupt_Int5 +public interrupt_Int6 +public interrupt_Int7 +public interrupt_Int8 +public interrupt_Int9 +public interrupt_Int10 +public interrupt_Int11 +public interrupt_Int12 +public interrupt_Int13 +public interrupt_Int14 +public interrupt_Int15 +public interrupt_Int16 +public interrupt_Int17 +public interrupt_Int18 +public interrupt_Int19 +public interrupt_Int20 +public interrupt_Int21 +public interrupt_Int22 +public interrupt_Int23 +public interrupt_Int24 +public interrupt_Int25 +public interrupt_Int26 +public interrupt_Int27 +public interrupt_Int28 +public interrupt_Int29 +public interrupt_Int30 +public interrupt_Int31 + +public interrupt_Int128 + + +section '.text' executable + + +macro inth op1 { + push rdi + mov rdi, op1 + push rsi + push rdx + mov rdx, [rsp+24] + push rcx + push r8 + push r9 + push r10 + push r11 + push rax + call interrupt_Handler + pop rax + pop r11 + pop r10 + pop r9 + pop r8 + pop rcx + pop rdx + pop rsi + pop rdi + iretq +} + +macro inth_err op1 { + push rsi + push rdi + mov rdi, op1 + mov esi, [rsp+16] + push rdx + mov rdx, [rsp+32] + push rcx + push r8 + push r9 + push r10 + push r11 + push rax + call interrupt_Handler + pop rax + pop r11 + pop r10 + pop r9 + pop r8 + pop rcx + pop rdx + pop rdi + pop rsi + add rsp, 8 ; pop the error code + iretq +} + +interrupt_Int0: ; does not return + inth 0 +interrupt_Int1: + inth 1 +interrupt_Int2: + inth 2 +interrupt_Int3: + inth 3 +interrupt_Int4: + inth 4 +interrupt_Int5: + inth 5 +interrupt_Int6: + inth 6 +interrupt_Int7: + inth 7 +interrupt_Int8: + inth 8 +interrupt_Int9: + inth 9 +interrupt_Int10: + inth_err 10 +interrupt_Int11: + inth_err 11 +interrupt_Int12: + inth_err 12 +interrupt_Int13: + inth_err 13 +interrupt_Int14: + inth_err 14 +interrupt_Int15: + inth 15 +interrupt_Int16: + inth 16 +interrupt_Int17: + inth_err 17 +interrupt_Int18: + inth 18 +interrupt_Int19: + inth 19 +interrupt_Int20: + inth 20 +interrupt_Int21: + inth_err 21 +interrupt_Int22: + inth 22 +interrupt_Int23: + inth 23 +interrupt_Int24: + inth 24 +interrupt_Int25: + inth 25 +interrupt_Int26: + inth 26 +interrupt_Int27: + inth 27 +interrupt_Int28: + inth 28 +interrupt_Int29: + inth 29 +interrupt_Int30: + inth 30 +interrupt_Int31: + inth 31 + +interrupt_Int128: + ;sub rsp, 32 + ;mov rcx, interrupt_string + ;call io_WriteConsoleASCII + ;add rsp, 32 + ;iretq + + ; no need to save the registers + ;push rax + ;push rdi + ;push rsi + ;push rdx + ;push rcx + ;push r8 + ;push r9 + ;push r10 + ;push r11 + call interrupt_Handler128 + ;pop r11 + ;pop r10 + ;pop r9 + ;pop r8 + ;pop rcx + ;pop rdx + ;pop rsi + ;pop rdi + ;pop rax + iretq diff --git a/interrupt/handler.c b/interrupt/handler.c new file mode 100644 index 0000000..7ed2340 --- /dev/null +++ b/interrupt/handler.c @@ -0,0 +1,52 @@ + +#include "interrupt.h" +#include "../runtime/panic_assert.h" + + +const char *interrupt_Descriptions[] = { + "Divide Error Execption", + "Debug Exception", + "NMI Interrupt", + "Breakpoint Exception", + "Overflow Exception", + "BOUND Range Exceeded Exception", + "Invalid Opcode Exception", + "Device Not Available Exception", + "Double Fault Exception", + "Coprocessor Segment Overrun", + "Invalid TSS Exception", + "Segment Not Present", + "Stack Fault Exception", + "General Protection Exception", + "Page-Fault Exception", + "Interrupt 15", + "x87 FPU Floating-Point Error", + "Alignment Check Exception", + "Machine-Check Exception", + "SIMD Floating-Point Exception", + "Interrupt 20", + "Control Protection Exception", + "Interrupt 22", + "Interrupt 23", + "Interrupt 24", + "Interrupt 25", + "Interrupt 26", + "Interrupt 27", + "Interrupt 28", + "Interrupt 29", + "Interrupt 30", + "Interrupt 31", +}; + +SYSV_ABI void interrupt_Handler(int vec, int errcode, uint64_t rip, int c, int d, int e) { + io_Printf("Panic: INT %02xh: %s, err=%d(0x%02x), rip=%llx\n", vec, interrupt_Descriptions[vec], errcode, errcode, rip); + __Panic_HaltSystem(); +} + +// handler for INT 80h +SYSV_ABI void interrupt_Handler128(int a, int b, int c, int d, int e, int f) { + int opcode; + asm volatile("mov %%eax, %0" + : "=rm"(opcode)); // read the opcode + io_Printf("INT 80h: EAX(opcode)=%d, abcdef=[%d,%d,%d,%d,%d,%d]\n", opcode, a, b, c, d, e, f); +} diff --git a/interrupt/handlers.h b/interrupt/handlers.h new file mode 100644 index 0000000..3434b85 --- /dev/null +++ b/interrupt/handlers.h @@ -0,0 +1,38 @@ +#pragma once + + +// the functions are not to be called, but to be taken address and put into IDT +void interrupt_Int0(); +void interrupt_Int1(); +void interrupt_Int2(); +void interrupt_Int3(); +void interrupt_Int4(); +void interrupt_Int5(); +void interrupt_Int6(); +void interrupt_Int7(); +void interrupt_Int8(); +void interrupt_Int9(); +void interrupt_Int10(); +void interrupt_Int11(); +void interrupt_Int12(); +void interrupt_Int13(); +void interrupt_Int14(); +void interrupt_Int15(); +void interrupt_Int16(); +void interrupt_Int17(); +void interrupt_Int18(); +void interrupt_Int19(); +void interrupt_Int20(); +void interrupt_Int21(); +void interrupt_Int22(); +void interrupt_Int23(); +void interrupt_Int24(); +void interrupt_Int25(); +void interrupt_Int26(); +void interrupt_Int27(); +void interrupt_Int28(); +void interrupt_Int29(); +void interrupt_Int30(); +void interrupt_Int31(); + +void interrupt_Int128(); diff --git a/interrupt/init.c b/interrupt/init.c new file mode 100644 index 0000000..0b4e0ea --- /dev/null +++ b/interrupt/init.c @@ -0,0 +1,118 @@ + +#include "interrupt.h" +#include "handlers.h" +#include "../memory/memory.h" +#include "../runtime/stdio.h" +#include "../runtime/panic_assert.h" +#include "testcode.h" + +interrupt_DescriptorTableReference *interrupt_IDTR, *interrupt_GDTR; +bool interrupt_Enabled; + + +/* +SYSV_ABI void interrupt_MapHandler(void *handler, int interrupt) { + //io_Printf("interrupt_MapHandler: handler %llx, int %d\n", handler, interrupt); + uint64_t *base = (uint64_t *)(KERNEL_IDT_MAPPING + interrupt * 16ull); + uint64_t b = 0; + + b |= (uint64_t)handler & 0xFFFFull; // Offset[15:0], 15:0 + b |= (uint64_t)GDT_EXEC_SELECTOR << 16; // Segment Selector, 31:16 + b |= IDT_TYPE_32_INTERRUPT_GATE; // Type = 32-bit Interrupt Gate, 44:40 + b |= IDT_RING0; // Ring 0, 46:45 + b |= IDT_PRESENT; // Present, 47 + b |= ((uint64_t)handler & 0xFFFF0000ull) << 32; // Offset[31:16], 63:48 + + *base = b; + *(base + 1) = (uint64_t)handler >> 32; +} +*/ + +// defined in assembly +SYSV_ABI void interrupt_MapHandler(void *handler, int interrupt); + + +void interrupt_Init() { + assert(sizeof(interrupt_DescriptorTableReference) == 10 && "GDTR/IDTR size must be 10 bytes"); + assert(offsetof(interrupt_DescriptorTableReference, base) == 2 && "GDTR/IDTR must be packed"); + + assert(KERNEL_IDTR_MAPPING % 4 == 0 && "IDTR not aligned to 4-byte"); + assert(KERNEL_GDTR_MAPPING % 4 == 0 && "GDTR not aligned to 4-byte"); + + + // allocate GDTR + io_WriteConsoleASCII("interrupt_Init() calling\n"); + interrupt_GDTR = (interrupt_DescriptorTableReference *)KERNEL_GDTR_MAPPING; + interrupt_GDTR->length = 4 * GDT_SIZE_BYTES - 1; + interrupt_GDTR->base = (void *)KERNEL_GDT_MAPPING; + io_WriteConsoleASCII("GDTR Written\n"); + + // set the 2 dummy gdts + uint64_t *gdt = (uint64_t *)KERNEL_GDT_MAPPING; + gdt[0] = 0; + gdt[1] = GDT_EXEC; + gdt[2] = GDT_DATA; + gdt[3] = GDT_EXEC_RING3; + gdt[4] = GDT_DATA_RING3; + io_WriteConsoleASCII("GDT Installed\n"); + + interrupt_LoadGDT(interrupt_GDTR); // set it! + io_WriteConsoleASCII("GDT OK\n"); + + //interrupt_Testcode(); + io_WriteConsoleASCII("Testcode OK\n"); + + + // allocate IDTR + //interrupt_IDTR = kMalloc(sizeof(interrupt_DescriptorTableReference)); + interrupt_IDTR = (interrupt_DescriptorTableReference *)KERNEL_IDTR_MAPPING; + interrupt_IDTR->length = KERNEL_IDT_SIZE - 1; + interrupt_IDTR->base = (void *)KERNEL_IDT_MAPPING; + io_WriteConsoleASCII("IDT Written\n"); + + interrupt_MapHandler(interrupt_Int0, 0); + interrupt_MapHandler(interrupt_Int1, 1); + interrupt_MapHandler(interrupt_Int2, 2); + interrupt_MapHandler(interrupt_Int3, 3); + interrupt_MapHandler(interrupt_Int4, 4); + interrupt_MapHandler(interrupt_Int5, 5); + interrupt_MapHandler(interrupt_Int6, 6); + interrupt_MapHandler(interrupt_Int7, 7); + interrupt_MapHandler(interrupt_Int8, 8); + interrupt_MapHandler(interrupt_Int9, 9); + interrupt_MapHandler(interrupt_Int10, 10); + interrupt_MapHandler(interrupt_Int11, 11); + interrupt_MapHandler(interrupt_Int12, 12); + interrupt_MapHandler(interrupt_Int13, 13); + interrupt_MapHandler(interrupt_Int14, 14); + interrupt_MapHandler(interrupt_Int15, 15); + interrupt_MapHandler(interrupt_Int16, 16); + interrupt_MapHandler(interrupt_Int17, 17); + interrupt_MapHandler(interrupt_Int18, 18); + interrupt_MapHandler(interrupt_Int19, 19); + interrupt_MapHandler(interrupt_Int20, 20); + interrupt_MapHandler(interrupt_Int21, 21); + interrupt_MapHandler(interrupt_Int22, 22); + interrupt_MapHandler(interrupt_Int23, 23); + interrupt_MapHandler(interrupt_Int24, 24); + interrupt_MapHandler(interrupt_Int25, 25); + interrupt_MapHandler(interrupt_Int26, 26); + interrupt_MapHandler(interrupt_Int27, 27); + interrupt_MapHandler(interrupt_Int28, 28); + interrupt_MapHandler(interrupt_Int29, 29); + interrupt_MapHandler(interrupt_Int30, 30); + interrupt_MapHandler(interrupt_Int31, 31); + interrupt_MapHandler(interrupt_Int128, 128); + io_WriteConsoleASCII("IDT Installed\n"); + + interrupt_LoadIDT(interrupt_IDTR); // set it! + + + io_WriteConsoleASCII("IDT OK\n"); + + interrupt_Enabled = true; + asm volatile("sti"); + + interrupt_ReloadSegments(); + io_WriteConsoleASCII("Segment Registers Reloaded\n"); +} diff --git a/interrupt/interrupt.h b/interrupt/interrupt.h new file mode 100644 index 0000000..9e8c944 --- /dev/null +++ b/interrupt/interrupt.h @@ -0,0 +1,74 @@ +#pragma once + +#include "../main.h" +#include "stdbool.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +#define GDT_SIZE_BYTES 4 +#define GDT_EXEC 0x00AF9A000000FFFFull // Base=0, Limit=max, Access=Present|Ring0|TypeUser|Exec|Readable, Flag=GranularityPage|Long +#define GDT_DATA 0x00EF92000000FFFFull // Base=0, Limit=max, Access=Present|Ring0|TypeUser|Writable, Flag=GranularityPage|Size +#define GDT_EXEC_RING3 0x00AFFA000000FFFFull // Base=0, Limit=max, Access=Present|Ring3|TypeUser|Exec|Readable, Flag=GranularityPage|Long +#define GDT_DATA_RING3 0x00EFF2000000FFFFull // Base=0, Limit=max, Access=Present|Ring3|TypeUser|Writable, Flag=GranularityPage|Size + +#define GDT_EXEC_SELECTOR 0x08 // SelectorIndex=1, TableIndicator=GDT(0), Privilege=Ring0 +#define GDT_DATA_SELECTOR 0x10 // SelectorIndex=2, TableIndicator=GDT(0), Privilege=Ring0 +#define GDT_EXEC_RING3_SELECTOR 0x1B // SelectorIndex=3, TableIndicator=GDT(0), Privilege=Ring3 +#define GDT_DATA_RING3_SELECTOR 0x23 // SelectorIndex=4, TableIndicator=GDT(0), Privilege=Ring3 + +#define IDT_PRESENT (1ull << 47) +#define IDT_RING0 0 +#define IDT_RING1 (1ull << 45) +#define IDT_RING2 (2ull << 45) +#define IDT_RING3 (3ull << 45) + +#define IDT_TYPE_32_CALL_GATE (0x0Cull << 40) +#define IDT_TYPE_32_INTERRUPT_GATE (0x0Eull << 40) +#define IDT_TYPE_32_TRAP_GATE (0x0Full << 40) + +typedef struct { + uint16_t length; + void * base; +} PACKED interrupt_DescriptorTableReference; +// address of IDTR and GDTR, allocated by kMalloc() and is never freed +extern interrupt_DescriptorTableReference *interrupt_IDTR, *interrupt_GDTR; + +// true if Init() has been called and interrupt handling is on +extern bool interrupt_Enabled; + +// initializes interrupt handling like IDT and a dummy GDT +void interrupt_Init(); + +SYSV_ABI void interrupt_MapHandler(void *handler, int interrupt); + + +// errorcode is 0 if nonexistent +// +// for IRQs, params are documented in assembly +SYSV_ABI void interrupt_Handler(int vec, int errcode, uint64_t rip, int c, int d, int e); + +// defined in assembly +SYSV_ABI void interrupt_LoadGDT(void *gdtr); +SYSV_ABI void interrupt_LoadIDT(void *idtr); +SYSV_ABI void interrupt_ReloadSegments(); + + +#define INTERRUPT_DISABLE \ + uintptr_t __interrupt_flags; \ + asm volatile("pushf\n\tcli\n\tpop %0" \ + : "=r"(__interrupt_flags) \ + : \ + : "memory") +#define INTERRUPT_RESTORE \ + asm volatile("push %0\n\tpopf" \ + : \ + : "rm"(__interrupt_flags) \ + : "memory", "cc") + + +#ifdef __cplusplus +} +#endif diff --git a/interrupt/interrupt_testcode.S b/interrupt/interrupt_testcode.S new file mode 100644 index 0000000..a88423c --- /dev/null +++ b/interrupt/interrupt_testcode.S @@ -0,0 +1,57 @@ +format elf64 + +extrn io_WriteConsoleASCII + + +section '.rodata' +interrupt_string: + db "Interrupt Testcode", 0x0A, 0x00 + + +section '.data' writable +align 4 +idt: + rb 50*16 + +idtr: + dw (50*16)-1 + dq idt + + +section '.text' executable +int_handler: + push rax + push rcx + push rdx + push r8 + push r9 + push r10 + push r11 + sub rsp, 32 + mov rcx, interrupt_string + call io_WriteConsoleASCII + add rsp, 32 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdx + pop rcx + pop rax + iretq + + +public interrupt_Testcode +interrupt_Testcode: + lidt [idtr] + mov rax, int_handler + mov [idt+49*16], ax + mov word [idt+49*16+2], 0x08 + mov word [idt+49*16+4], 0x8e00 + shr rax, 16 + mov [idt+49*16+6], ax + shr rax, 16 + mov [idt+49*16+8], rax + + int 49 + ret diff --git a/interrupt/load_gdt.S b/interrupt/load_gdt.S new file mode 100644 index 0000000..79a9d70 --- /dev/null +++ b/interrupt/load_gdt.S @@ -0,0 +1,45 @@ +format elf64 + +public interrupt_ReloadSegments +public interrupt_LoadGDT +public interrupt_LoadIDT + + +section '.text' executable + +; sysvx64call void interrupt_LoadGDT(void* gdtr) +; +; Input: (void* rdi) +; Clobbers: none +interrupt_LoadGDT: + lgdt [rdi] + ret + +; sysvx64call void interrupt_ReloadSegments() +; +; Clobbers: rax +interrupt_ReloadSegments: + mov eax, 0x10 ; my data segment + xor ax, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ;jmp 0x08:.flush + ; as in https://forum.osdev.org/viewtopic.php?f=1&t=30739 + ; farjump does not work in long mode, you need to do a far return: + pop rax + push qword 0x08 ; my code segment + push rax + retfq + +; sysvx64call void interrupt_LoadIDT(void* idtr) +; +; Input: (void* rdi) +; Clobbers: none +interrupt_LoadIDT: + lidt [rdi] + ret + diff --git a/interrupt/map_handler.S b/interrupt/map_handler.S new file mode 100644 index 0000000..b3b3905 --- /dev/null +++ b/interrupt/map_handler.S @@ -0,0 +1,25 @@ +format elf64 + +extrn io_WriteConsoleASCII + +public interrupt_MapHandler + + +section '.text' executable + +; sysvx64call void interrupt_MapHandler(uint64_t handler, int interrupt) +; +; Input: (uint64_t rdi, int rsi) +; Clobbers: rax, flags +interrupt_MapHandler: + mov rax, 0xFFFFFFFEC0000000 ; KERNEL_IDT_MAPPING + shl rsi, 4 ; rsi *= 16 + add rsi, rax ; rsi += KERNEL_IDT_MAPPING + mov [rsi], di + mov word [rsi+2], 0x08 ; GDT_EXEC_SELECTOR (index=1) + mov word [rsi+4], 0x8e00 + shr rdi, 16 + mov [rsi+6], di + shr rdi, 16 + mov [rsi+8], rdi + ret diff --git a/interrupt/syscall.S b/interrupt/syscall.S new file mode 100644 index 0000000..dbe3f69 --- /dev/null +++ b/interrupt/syscall.S @@ -0,0 +1,18 @@ +format elf64 + +public asm_Syscall as 'Syscall' ; syscall is a reserved token + +section ".text" executable + +; sysvx64call int Syscall(int syscall_id, int a,b,c,d,e,f) +asm_Syscall: + mov rax, rdi + mov rdi, rsi + mov rsi, rdx + mov rdx, rcx + mov rcx, r8 + mov r8, r9 + mov r9, [rsp+8] + int 0x80 + ret + diff --git a/interrupt/syscall.h b/interrupt/syscall.h new file mode 100644 index 0000000..406b398 --- /dev/null +++ b/interrupt/syscall.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../main.h" + +// this function is here, well, mostly just for fun. +// +// userspace in the far future should need this +SYSV_ABI long Syscall(int id, long a, long b, long c, long d, long e, long f); diff --git a/interrupt/testcode.h b/interrupt/testcode.h new file mode 100644 index 0000000..e63789a --- /dev/null +++ b/interrupt/testcode.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../main.h" + + +SYSV_ABI void interrupt_Testcode(); diff --git a/kernel/kmain.c b/kernel/kmain.c new file mode 100644 index 0000000..b035a53 --- /dev/null +++ b/kernel/kmain.c @@ -0,0 +1,51 @@ + +#include "../main.h" +#include "kmain.h" + +#include "../runtime/stdio.h" +#include "../runtime/panic_assert.h" +#include "../memory/memory.h" +#include "../memory/paging_internal.h" +#include "../interrupt/interrupt.h" +#include "../interrupt/handlers.h" +#include "../interrupt/syscall.h" +#include "../driver/irq/pic/pic.h" +#include "../driver/irq/pic/ps2/ps2.h" + +#include "../execformat/pe/reloc.h" +void execformat_pe_ReadSystemHeader(execformat_pe_PortableExecutable *pe); + +static void tellRIP() { + uint64_t a, b; + asm volatile("leaq (%%rip), %0\n\tleaq runtime_InitPaging(%%rip), %1" + : "=r"(a), "=r"(b)); + io_Printf("tellRIP(): Stack position: %llx, RIP=%llx, kMain_StackPosition:%llx(%llx), interrupt_Int128: %llx\n", &a, a, (uint64_t)&kMain_StackPosition, b, (uint64_t)interrupt_Int128); +} + + +SYSV_ABI void kMain() { + io_WriteConsoleASCII("Yes! kMain survived!\n"); + + uint64_t a; + asm volatile("leaq (%%rip), %0" + : "=r"(a)); + io_Printf("Stack position: %llx, RIP=%llx, runtime_InitPaging:%llx, interrupt_Int128: %llx\n", &a, a, (uint64_t)runtime_InitPaging, (uint64_t)interrupt_Int128); + + interrupt_Init(); + io_WriteConsoleASCII("Interrupts initialized\n"); + + Syscall(4, 1, 2, 3, 4, 5, 6); + io_WriteConsoleASCII("Returning from Syscall()\n"); + + tellRIP(); + + irq_pic_Init(); + io_WriteConsoleASCII("PIC IRQ OK\n"); + irq_pic_ps2_Init(); + io_WriteConsoleASCII("PIC PS/2 OK\n"); + + for (;;) { + asm volatile("hlt"); + io_WriteConsoleASCII("kMain: Interrupt hit\n"); + } +} diff --git a/kernel/kmain.h b/kernel/kmain.h new file mode 100644 index 0000000..ae4e2d9 --- /dev/null +++ b/kernel/kmain.h @@ -0,0 +1,28 @@ +#pragma once + +#include "../main.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// set the position of the top of stack before calling kMain_Init() +extern uint64_t kMain_StackPosition; +extern char kMain_StackData[], kMain_StackData_End[]; + + +typedef SYSV_ABI void (*kMainType)(); + +// written in Assembly, this function deals with stack, registers, etc, and then calls kMain. +// +// remember setting kMain_StackPosition before calling kMain_Init() +SYSV_ABI noreturn void kMain_Init(); + +// this is the real main function. +// it should only be called by kMain_Init() +SYSV_ABI void kMain(); + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/kernel/kmain.init.S b/kernel/kmain.init.S new file mode 100644 index 0000000..0cd3097 --- /dev/null +++ b/kernel/kmain.init.S @@ -0,0 +1,25 @@ +format elf64 + +extrn kMain + +public kMain_StackPosition +public kMain_Init + + +section '.bss' writable +kMain_StackPosition: + rq 1 + + +section '.text' executable + +; sysvx64call void kMain_Init() +kMain_Init: + mov rsp, [kMain_StackPosition] + call kMain + +.hlt: + hlt + jmp .hlt + + diff --git a/libc/README b/libc/README new file mode 100644 index 0000000..8c29afa --- /dev/null +++ b/libc/README @@ -0,0 +1,11 @@ + +The C library code here mostly comes from The Public Domain C Library (pdclib). + +Get it here: https://rootdirectory.de/doku.php?id=pdclib:start +and here's the repo: https://github.com/DevSolar/pdclib + + +The original code is licensed under CC0 (Public Domain), and I'm really having a hard time +keeping track which file has been modified and how, etc. + + diff --git a/libc/abs.c b/libc/abs.c new file mode 100644 index 0000000..649c3fb --- /dev/null +++ b/libc/abs.c @@ -0,0 +1,9 @@ + +#include + +int abs(int val) { + if (val < 0) + return -val; + else + return val; +} diff --git a/libc/include/assert.h b/libc/include/assert.h new file mode 100644 index 0000000..34bea52 --- /dev/null +++ b/libc/include/assert.h @@ -0,0 +1,50 @@ +/* Diagnostics + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_ASSERT_H +#define _PDCLIB_ASSERT_H _PDCLIB_ASSERT_H +_PDCLIB_PUBLIC void _PDCLIB_assert99( const char * const, const char * const, const char * const ); +_PDCLIB_PUBLIC void _PDCLIB_assert89( const char * const ); +#endif + +/* If NDEBUG is set, assert() is a null operation. */ +#undef assert + +#ifdef NDEBUG +#define assert( ignore ) ( (void) 0 ) +#else +#if __STDC_VERSION__ >= 199901L +#define assert( expression ) ( ( expression ) ? (void) 0 \ + : _PDCLIB_assert99( "Assertion failed: " #expression \ + ", function ", __func__, \ + ", file " __FILE__ \ + ", line " _PDCLIB_value2string( __LINE__ ) \ + ".\n" ) ) +#else +#define assert( expression ) ( ( expression ) ? (void) 0 \ + : _PDCLIB_assert89( "Assertion failed: " #expression \ + ", file " __FILE__ \ + ", line " _PDCLIB_value2string( __LINE__ ) \ + ".\n" ) ) +#endif +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_ASSERT_H +#include _PDCLIB_EXTEND_ASSERT_H +#endif + +#ifdef __cplusplus +} +#endif diff --git a/libc/include/ctype.h b/libc/include/ctype.h new file mode 100644 index 0000000..551667a --- /dev/null +++ b/libc/include/ctype.h @@ -0,0 +1,110 @@ +/* Character handling + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_CTYPE_H +#define _PDCLIB_CTYPE_H _PDCLIB_CTYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +/* Character classification functions */ + +/* Note that there is a difference between "whitespace" (any printing, non- + graph character, like horizontal and vertical tab), and "blank" (the literal + ' ' space character). + + There will be masking macros for each of these later on, but right now I + focus on the functions only. +*/ + +/* Returns isalpha( c ) || isdigit( c ) */ +_PDCLIB_PUBLIC int isalnum( int c ); + +/* Returns isupper( c ) || islower( c ) in the "C" locale. + In any other locale, also returns true for a locale-specific set of + alphabetic characters which are neither control characters, digits, + punctation, or whitespace. +*/ +_PDCLIB_PUBLIC int isalpha( int c ); + +/* Returns true if the character isspace() and used for separating words within + a line of text. In the "C" locale, only ' ' and '\t' are considered blanks. +*/ +_PDCLIB_PUBLIC int isblank( int c ); + +/* Returns true if the character is a control character. */ +_PDCLIB_PUBLIC int iscntrl( int c ); + +/* Returns true if the character is a decimal digit. Locale-independent. */ +_PDCLIB_PUBLIC int isdigit( int c ); + +/* Returns true for every printing character except space (' '). + NOTE: This definition differs from that of iswgraph() in , + which considers any iswspace() character, not only ' '. +*/ +_PDCLIB_PUBLIC int isgraph( int c ); + +/* Returns true for lowercase letters in the "C" locale. + In any other locale, also returns true for a locale-specific set of + characters which are neither control characters, digits, punctation, or + space (' '). In a locale other than the "C" locale, a character might test + true for both islower() and isupper(). +*/ +_PDCLIB_PUBLIC int islower( int c ); + +/* Returns true for every printing character including space (' '). */ +_PDCLIB_PUBLIC int isprint( int c ); + +/* Returns true for a locale-specific set of punctuation charcters; these + may not be whitespace or alphanumeric. In the "C" locale, returns true + for every printing character that is not whitespace or alphanumeric. +*/ +_PDCLIB_PUBLIC int ispunct( int c ); + +/* Returns true for every standard whitespace character (' ', '\f', '\n', '\r', + '\t', '\v') in the "C" locale. In any other locale, also returns true for a + locale-specific set of characters for which isalnum() is false. +*/ +_PDCLIB_PUBLIC int isspace( int c ); + +/* Returns true for uppercase letters in the "C" locale. + In any other locale, also returns true for a locale-specific set of + characters which are neither control characters, digits, punctation, or + space (' '). In a locale other than the "C" locale, a character might test + true for both islower() and isupper(). +*/ +_PDCLIB_PUBLIC int isupper( int c ); + +/* Returns true for any hexadecimal-digit character. Locale-independent. */ +_PDCLIB_PUBLIC int isxdigit( int c ); + +/* Character case mapping functions */ + +/* Converts an uppercase letter to a corresponding lowercase letter. Input that + is not an uppercase letter remains unchanged. +*/ +_PDCLIB_PUBLIC int tolower( int c ); + +/* Converts a lowercase letter to a corresponding uppercase letter. Input that + is not a lowercase letter remains unchanged. +*/ +_PDCLIB_PUBLIC int toupper( int c ); + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_CTYPE_H +#include _PDCLIB_EXTEND_CTYPE_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/errno.h b/libc/include/errno.h new file mode 100644 index 0000000..28eaf5b --- /dev/null +++ b/libc/include/errno.h @@ -0,0 +1,202 @@ +/* Errors + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_ERRNO_H +#define _PDCLIB_ERRNO_H _PDCLIB_ERRNO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +/* FIXME: With , this needs to be in thread-specific storage. */ +#define errno (*_PDCLIB_errno_func()) + +/* C only requires the following three */ + +/* Result too large */ +#define ERANGE _PDCLIB_ERANGE +/* Mathematics argument out of domain of function */ +#define EDOM _PDCLIB_EDOM +/* Illegal byte sequence */ +#define EILSEQ _PDCLIB_EILSEQ + +/* C++ additionally requires the folloing */ + +/* Argument list too long */ +#define E2BIG _PDCLIB_E2BIG +/* Permission denied */ +#define EACCES _PDCLIB_EACCES +/* Address in use */ +#define EADDRINUSE _PDCLIB_EADDRINUSE +/* Address not available */ +#define EADDRNOTAVAIL _PDCLIB_EADDRNOTAVAIL +/* Address family not supported */ +#define EAFNOSUPPORT _PDCLIB_EAFNOSUPPORT +/* Resource unavailable, try again */ +#define EAGAIN _PDCLIB_EAGAIN +/* Connection already in progress */ +#define EALREADY _PDCLIB_EALREADY +/* Bad file descriptor */ +#define EBADF _PDCLIB_EBADF +/* Bad message */ +#define EBADMSG _PDCLIB_EBADMSG +/* Device or resource busy */ +#define EBUSY _PDCLIB_EBUSY +/* Operation canceled */ +#define ECANCELED _PDCLIB_ECANCELED +/* No child processes */ +#define ECHILD _PDCLIB_ECHILD +/* Connection aborted */ +#define ECONNABORTED _PDCLIB_ECONNABORTED +/* Connection refused */ +#define ECONNREFUSED _PDCLIB_ECONNREFUSED +/* Connection reset */ +#define ECONNRESET _PDCLIB_ECONNRESET +/* Resource deadlock would occur */ +#define EDEADLK _PDCLIB_EDEADLK +/* Destination address required */ +#define EDESTADDRREQ _PDCLIB_EDESTADDRREQ +/* File exists */ +#define EEXIST _PDCLIB_EEXIST +/* Bad address */ +#define EFAULT _PDCLIB_EFAULT +/* File too large */ +#define EFBIG _PDCLIB_EFBIG +/* Host is unreachable */ +#define EHOSTUNREACH _PDCLIB_EHOSTUNREACH +/* Identifier removed */ +#define EIDRM _PDCLIB_EIDRM +/* Operation in progress */ +#define EINPROGRESS _PDCLIB_EINPROGRESS +/* Interrupted function */ +#define EINTR _PDCLIB_EINTR +/* Invalid argument */ +#define EINVAL _PDCLIB_EINVAL +/* I/O error */ +#define EIO _PDCLIB_EIO +/* Socket is connected */ +#define EISCONN _PDCLIB_EISCONN +/* Is a directory */ +#define EISDIR _PDCLIB_EISDIR +/* Too many levels of symbolic links */ +#define ELOOP _PDCLIB_ELOOP +/* File descriptor value too large */ +#define EMFILE _PDCLIB_EMFILE +/* Too many links */ +#define EMLINK _PDCLIB_EMLINK +/* Message too large */ +#define EMSGSIZE _PDCLIB_EMSGSIZE +/* Filename too long */ +#define ENAMETOOLONG _PDCLIB_ENAMETOOLONG +/* Network is down */ +#define ENETDOWN _PDCLIB_ENETDOWN +/* Connection aborted by network */ +#define ENETRESET _PDCLIB_ENETRESET +/* Network unreachable */ +#define ENETUNREACH _PDCLIB_ENETUNREACH +/* Too many files open in system */ +#define ENFILE _PDCLIB_ENFILE +/* No buffer space available */ +#define ENOBUFS _PDCLIB_ENOBUFS +/* No message is available on the STREAM head read queue */ +#define ENODATA _PDCLIB_ENODATA +/* No such device */ +#define ENODEV _PDCLIB_ENODEV +/* No such file or directory */ +#define ENOENT _PDCLIB_ENOENT +/* Executable file format error */ +#define ENOEXEC _PDCLIB_ENOEXEC +/* No locks available */ +#define ENOLCK _PDCLIB_ENOLCK +/* Link has been severed */ +#define ENOLINK _PDCLIB_ENOLINK +/* Not enough space */ +#define ENOMEM _PDCLIB_ENOMEM +/* No message of the desired type */ +#define ENOMSG _PDCLIB_ENOMSG +/* Protocol not available */ +#define ENOPROTOOPT _PDCLIB_ENOPROTOOPT +/* No space left on device */ +#define ENOSPC _PDCLIB_ENOSPC +/* No STREAM resources */ +#define ENOSR _PDCLIB_ENOSR +/* Not a STREAM */ +#define ENOSTR _PDCLIB_ENOSTR +/* Function not supported */ +#define ENOSYS _PDCLIB_ENOSYS +/* The socket is not connected */ +#define ENOTCONN _PDCLIB_ENOTCONN +/* Not a directory */ +#define ENOTDIR _PDCLIB_ENOTDIR +/* Directory not empty */ +#define ENOTEMPTY _PDCLIB_ENOTEMPTY +/* State not recoverable */ +#define ENOTRECOVERABLE _PDCLIB_ENOTRECOVERABLE +/* Not a socket */ +#define ENOTSOCK _PDCLIB_ENOTSOCK +/* Not supported */ +#define ENOTSUP _PDCLIB_ENOTSUP +/* Inappropriate I/O control operation */ +#define ENOTTY _PDCLIB_ENOTTY +/* No such device or address */ +#define ENXIO _PDCLIB_ENXIO +/* Operation not supported on socket */ +#define EOPNOTSUPP _PDCLIB_EOPNOTSUPP +/* Value too large to be stored in data type */ +#define EOVERFLOW _PDCLIB_EOVERFLOW +/* Previous owner died */ +#define EOWNERDEAD _PDCLIB_EOWNERDEAD +/* Operation not permitted */ +#define EPERM _PDCLIB_EPERM +/* Broken pipe */ +#define EPIPE _PDCLIB_EPIPE +/* Protocol error */ +#define EPROTO _PDCLIB_EPROTO +/* Protocol not supported */ +#define EPROTONOSUPPORT _PDCLIB_EPROTONOSUPPORT +/* Protocol wrong type for socket */ +#define EPROTOTYPE _PDCLIB_EPROTOTYPE +/* Read-only file system */ +#define EROFS _PDCLIB_EROFS +/* Invalid seek */ +#define ESPIPE _PDCLIB_ESPIPE +/* No such process */ +#define ESRCH _PDCLIB_ESRCH +/* Stream ioctl() timeout */ +#define ETIME _PDCLIB_ETIME +/* Connection timed out */ +#define ETIMEDOUT _PDCLIB_ETIMEDOUT +/* Text file busy */ +#define ETXTBSY _PDCLIB_ETXTBSY +/* Operation would block */ +#define EWOULDBLOCK _PDCLIB_EWOULDBLOCK +/* Cross-device link */ +#define EXDEV _PDCLIB_EXDEV + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 +#ifndef _PDCLIB_ERRNO_T_DEFINED +#define _PDCLIB_ERRNO_T_DEFINED _PDCLIB_ERRNO_T_DEFINED +typedef int errno_t; +#endif +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_ERRNO_H +#include _PDCLIB_EXTEND_ERRNO_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/float.h b/libc/include/float.h new file mode 100644 index 0000000..fc34278 --- /dev/null +++ b/libc/include/float.h @@ -0,0 +1,153 @@ +/* Characteristics of floating types + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_FLOAT_H +#define _PDCLIB_FLOAT_H _PDCLIB_FLOAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +/* The following parameters are used to define the model for each + floating-point type: + + s sign (±1) + b base or radix of exponent representation (an integer > 1) + e exponent (an integer between a minimum eₘₙ and a maximum eₘₓ) + p precision (the number of base-b digits in the significand) + ƒₖ nonnegative integers less than b (the significand digits) + + A floating-point number (x) is defined by the following model: + + x = sbᵉ ₖ₌₁∑ᵖ ƒₖb⁻ᵏ, eₘₙ ≤ e ≤ eₘₓ + + In addition to normalized floating-point numbers (ƒ₁ > 0 if x ≠ 0), + floating types may be able to contain other kinds of floating-point + numbers, such as subnormal floating-point numbers (x ≠ 0, e = eₘₙ, + ƒ₁ = 0) and unnormalized floating-point numbers (x ≠ 0, e > eₘₙ, + ƒ₁ = 0), and values that are not floating-point numbers, such as + infinities and NaNs. +*/ + +/* Whether rounding toward zero (0), to nearest (1), toward positive + infinity (2), toward negative infinity (3), or indeterminate (-1). + FLT_ROUNDS is not a compile-time constant, and may change due to + calls to fesetround() (in ). +*/ +#define FLT_ROUNDS _PDCLIB_FLT_ROUNDS + +/* Whether operations are done in the given type (0), float is + evaluated as double (1), float and double are evaluated as + long double (2), or evaluation method is indeterminate (-1). +*/ +#define FLT_EVAL_METHOD _PDCLIB_FLT_EVAL_METHOD + +/* Whether the type supports subnormal numbers (1), does not support + them (0), or support is indeterminate (-1). +*/ +#define FLT_HAS_SUBNORM _PDCLIB_FLT_HAS_SUBNORM +#define DBL_HAS_SUBNORM _PDCLIB_DBL_HAS_SUBNORM +#define LDBL_HAS_SUBNORM _PDCLIB_LDBL_HAS_SUBNORM + +/* Radix of exponent representation, b */ +#define FLT_RADIX _PDCLIB_FLT_RADIX + +/* Number of base-b digits in the floating point significand, p */ +#define FLT_MANT_DIG _PDCLIB_FLT_MANT_DIG +#define DBL_MANT_DIG _PDCLIB_DBL_MANT_DIG +#define LDBL_MANT_DIG _PDCLIB_LDBL_MANT_DIG + +/* Number of decimal digits, n, so that any floating point number with + p radix b digits can be rounded to a floating point number with n + decimal digits and back without changing the value + pₘₓlog₁₀b if b is a power of 10, + ⌈1 + pₘₓlog₁₀b⌉ otherwise. +*/ +#define FLT_DECIMAL_DIG _PDCLIB_FLT_DECIMAL_DIG +#define DBL_DECIMAL_DIG _PDCLIB_DBL_DECIMAL_DIG +#define LDBL_DECIMAL_DIG _PDCLIB_LDBL_DECIMAL_DIG + +/* As above, for the widest supported type. */ +#define DECIMAL_DIG _PDCLIB_DECIMAL_DIG + +/* Number of decimal digits, q, so that any floating point number with + q decimal digits can be rounded to a floating point number with p + radix b digits and back without changing the value of the q decimal + digits. + p log₁₀b if b is a power of 10, + ⌊(p - 1)log₁₀b⌋ otherwise. +*/ +#define FLT_DIG _PDCLIB_FLT_DIG +#define DBL_DIG _PDCLIB_DBL_DIG +#define LDBL_DIG _PDCLIB_LDBL_DIG + +/* Minimum negative integer such that FLT_RADIX raised to one less + than that power is a normalized floating point number, eₘₙ +*/ +#define FLT_MIN_EXP _PDCLIB_FLT_MIN_EXP +#define DBL_MIN_EXP _PDCLIB_DBL_MIN_EXP +#define LDBL_MIN_EXP _PDCLIB_LDBL_MIN_EXP + +/* Minimum negative integer such that 10 raised to one less than that + power is in the range of normalized floating point numbers, + ⌈log₁₀b^{eₘₙ⁻¹}⌉ +*/ +#define FLT_MIN_10_EXP _PDCLIB_FLT_MIN_10_EXP +#define DBL_MIN_10_EXP _PDCLIB_DBL_MIN_10_EXP +#define LDBL_MIN_10_EXP _PDCLIB_LDBL_MIN_10_EXP + +/* Maximum integer such that FLT_RADIX raised to one less than that + power is a representable finite floating point number, eₘₓ +*/ +#define FLT_MAX_EXP _PDCLIB_FLT_MAX_EXP +#define DBL_MAX_EXP _PDCLIB_DBL_MAX_EXP +#define LDBL_MAX_EXP _PDCLIB_LDBL_MAX_EXP + +/* Maximum integer such that 10 raised to that power is in the range + of representable finite floating-point numbers, + ⌊log₁₀((1-b⁻ᵖ)b^{eₘₓ})⌋ +*/ +#define FLT_MAX_10_EXP _PDCLIB_FLT_MAX_10_EXP +#define DBL_MAX_10_EXP _PDCLIB_DBL_MAX_10_EXP +#define LDBL_MAX_10_EXP _PDCLIB_LDBL_MAX_10_EXP + +/* Maximum representable finite floating-point number, (1-b⁻ᵖ)b^{eₘₓ} +*/ +#define FLT_MAX _PDCLIB_FLT_MAX +#define DBL_MAX _PDCLIB_DBL_MAX +#define LDBL_MAX _PDCLIB_LDBL_MAX + +/* Difference between 1 and the least value greater than 1 that is + representable in the type, b¹⁻ᵖ +*/ +#define FLT_EPSILON _PDCLIB_FLT_EPSILON +#define DBL_EPSILON _PDCLIB_DBL_EPSILON +#define LDBL_EPSILON _PDCLIB_LDBL_EPSILON + +/* Minimum normalized positive floating-point number, b^{eₘₙ⁻¹} */ +#define FLT_MIN _PDCLIB_FLT_MIN +#define DBL_MIN _PDCLIB_DBL_MIN +#define LDBL_MIN _PDCLIB_LDBL_MIN + +/* Minimum positive floating-point number */ +#define FLT_TRUE_MIN _PDCLIB_FLT_TRUE_MIN +#define DBL_TRUE_MIN _PDCLIB_DBL_TRUE_MIN +#define LDBL_TRUE_MIN _PDCLIB_LDBL_TRUE_MIN + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_FLOAT_H +#include _PDCLIB_EXTEND_FLOAT_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/inttypes.h b/libc/include/inttypes.h new file mode 100644 index 0000000..0be401d --- /dev/null +++ b/libc/include/inttypes.h @@ -0,0 +1,368 @@ +/* Format conversion of integer types + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_INTTYPES_H +#define _PDCLIB_INTTYPES_H _PDCLIB_INTTYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct _PDCLIB_imaxdiv_t imaxdiv_t; + +/* 7.8.1 Macros for format specifiers */ + +/* The various leastN_t, fastN_t, intmax_t, and intptr_t types are typedefs + to native types. But the user does not know which ones, which gives some + problems when trying to *printf() / *scanf() those types. The various + macros defined below allow to give the correct conversion specifiers + without knowing the actual native type they represent. +*/ + +#if _PDCLIB_INT_LEAST8_MAX > _PDCLIB_INT_MAX +#define PRIdLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, d ) ) +#define PRIiLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, i ) ) +#define PRIoLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, o ) ) +#define PRIuLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, u ) ) +#define PRIxLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, x ) ) +#define PRIXLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, X ) ) +#else +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#endif + +#if _PDCLIB_INT_FAST8_MAX > _PDCLIB_INT_MAX +#define PRIdFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, d ) ) +#define PRIiFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, i ) ) +#define PRIoFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, o ) ) +#define PRIuFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, u ) ) +#define PRIxFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, x ) ) +#define PRIXFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, X ) ) +#else +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" +#endif + +#if _PDCLIB_INT_LEAST16_MAX > _PDCLIB_INT_MAX +#define PRIdLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, d ) ) +#define PRIiLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, i ) ) +#define PRIoLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, o ) ) +#define PRIuLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, u ) ) +#define PRIxLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, x ) ) +#define PRIXLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, X ) ) +#else +#define PRIdLEAST16 "d" +#define PRIiLEAST16 "i" +#define PRIoLEAST16 "o" +#define PRIuLEAST16 "u" +#define PRIxLEAST16 "x" +#define PRIXLEAST16 "X" +#endif + +#if _PDCLIB_INT_FAST16_MAX > _PDCLIB_INT_MAX +#define PRIdFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, d ) ) +#define PRIiFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, i ) ) +#define PRIoFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, o ) ) +#define PRIuFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, u ) ) +#define PRIxFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, x ) ) +#define PRIXFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, X ) ) +#else +#define PRIdFAST16 "d" +#define PRIiFAST16 "i" +#define PRIoFAST16 "o" +#define PRIuFAST16 "u" +#define PRIxFAST16 "x" +#define PRIXFAST16 "X" +#endif + +#if _PDCLIB_INT_LEAST32_MAX > _PDCLIB_INT_MAX +#define PRIdLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, d ) ) +#define PRIiLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, i ) ) +#define PRIoLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, o ) ) +#define PRIuLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, u ) ) +#define PRIxLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, x ) ) +#define PRIXLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, X ) ) +#else +#define PRIdLEAST32 "d" +#define PRIiLEAST32 "i" +#define PRIoLEAST32 "o" +#define PRIuLEAST32 "u" +#define PRIxLEAST32 "x" +#define PRIXLEAST32 "X" +#endif + +#if _PDCLIB_INT_FAST32_MAX > _PDCLIB_INT_MAX +#define PRIdFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, d ) ) +#define PRIiFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, i ) ) +#define PRIoFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, o ) ) +#define PRIuFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, u ) ) +#define PRIxFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, x ) ) +#define PRIXFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, X ) ) +#else +#define PRIdFAST32 "d" +#define PRIiFAST32 "i" +#define PRIoFAST32 "o" +#define PRIuFAST32 "u" +#define PRIxFAST32 "x" +#define PRIXFAST32 "X" +#endif + +#if _PDCLIB_INT_LEAST64_MAX > _PDCLIB_INT_MAX +#define PRIdLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, d ) ) +#define PRIiLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, i ) ) +#define PRIoLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, o ) ) +#define PRIuLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, u ) ) +#define PRIxLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, x ) ) +#define PRIXLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, X ) ) +#else +#define PRIdLEAST64 "d" +#define PRIiLEAST64 "i" +#define PRIoLEAST64 "o" +#define PRIuLEAST64 "u" +#define PRIxLEAST64 "x" +#define PRIXLEAST64 "X" +#endif + +#if _PDCLIB_INT_FAST64_MAX > _PDCLIB_INT_MAX +#define PRIdFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, d ) ) +#define PRIiFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, i ) ) +#define PRIoFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, o ) ) +#define PRIuFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, u ) ) +#define PRIxFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, x ) ) +#define PRIXFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, X ) ) +#else +#define PRIdFAST64 "d" +#define PRIiFAST64 "i" +#define PRIoFAST64 "o" +#define PRIuFAST64 "u" +#define PRIxFAST64 "x" +#define PRIXFAST64 "X" +#endif + +#if _PDCLIB_INTMAX_MAX > _PDCLIB_INT_MAX +#define PRIdMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, d ) ) +#define PRIiMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, i ) ) +#define PRIoMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, o ) ) +#define PRIuMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, u ) ) +#define PRIxMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, x ) ) +#define PRIXMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, X ) ) +#else +#define PRIdMAX "d" +#define PRIiMAX "i" +#define PRIoMAX "o" +#define PRIuMAX "u" +#define PRIxMAX "x" +#define PRIXMAX "X" +#endif + +#if _PDCLIB_INTPTR_MAX > _PDCLIB_INT_MAX +#define PRIdPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, d ) ) +#define PRIiPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, i ) ) +#define PRIoPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, o ) ) +#define PRIuPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, u ) ) +#define PRIxPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, x ) ) +#define PRIXPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, X ) ) +#else +#define PRIdPTR "d" +#define PRIiPTR "i" +#define PRIoPTR "o" +#define PRIuPTR "u" +#define PRIxPTR "x" +#define PRIXPTR "X" +#endif + +#define SCNdLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, d ) ) +#define SCNiLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, i ) ) +#define SCNoLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, o ) ) +#define SCNuLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, u ) ) +#define SCNxLEAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, x ) ) + +#define SCNdFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, d ) ) +#define SCNiFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, i ) ) +#define SCNoFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, o ) ) +#define SCNuFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, u ) ) +#define SCNxFAST8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST8_PREFIX, x ) ) + +#define SCNdLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, d ) ) +#define SCNiLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, i ) ) +#define SCNoLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, o ) ) +#define SCNuLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, u ) ) +#define SCNxLEAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, x ) ) + +#define SCNdFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, d ) ) +#define SCNiFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, i ) ) +#define SCNoFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, o ) ) +#define SCNuFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, u ) ) +#define SCNxFAST16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST16_PREFIX, x ) ) + +#define SCNdLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, d ) ) +#define SCNiLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, i ) ) +#define SCNoLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, o ) ) +#define SCNuLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, u ) ) +#define SCNxLEAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, x ) ) + +#define SCNdFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, d ) ) +#define SCNiFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, i ) ) +#define SCNoFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, o ) ) +#define SCNuFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, u ) ) +#define SCNxFAST32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST32_PREFIX, x ) ) + +#define SCNdLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, d ) ) +#define SCNiLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, i ) ) +#define SCNoLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, o ) ) +#define SCNuLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, u ) ) +#define SCNxLEAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, x ) ) + +#define SCNdFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, d ) ) +#define SCNiFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, i ) ) +#define SCNoFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, o ) ) +#define SCNuFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, u ) ) +#define SCNxFAST64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_FAST64_PREFIX, x ) ) + +#define SCNdMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, d ) ) +#define SCNiMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, i ) ) +#define SCNoMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, o ) ) +#define SCNuMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, u ) ) +#define SCNxMAX _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTMAX_PREFIX, x ) ) + +#define SCNdPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, d ) ) +#define SCNiPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, i ) ) +#define SCNoPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, o ) ) +#define SCNuPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, u ) ) +#define SCNxPTR _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INTPTR_PREFIX, x ) ) + +/* The exact-width types (int8_t, int16_t, ...) are *optional*, as not all + architectures support the necessary 8-bits-per-byte two's complement + native types. +*/ + +#if _PDCLIB_TWOS_COMPLEMENT == 1 + +#if _PDCLIB_INT_LEAST8_MAX == 0x7f +#if _PDCLIB_INT_LEAST8_MAX > _PDCLIB_INT_MAX +#define PRId8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, d ) ) +#define PRIi8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, i ) ) +#define PRIo8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, o ) ) +#define PRIu8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, u ) ) +#define PRIx8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, x ) ) +#define PRIX8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, X ) ) +#endif +#define SCNd8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, d ) ) +#define SCNi8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, i ) ) +#define SCNo8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, o ) ) +#define SCNu8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, u ) ) +#define SCNx8 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST8_PREFIX, x ) ) +#endif + +#if _PDCLIB_INT_LEAST16_MAX == 0x7fff +#if _PDCLIB_INT_LEAST16_MAX > _PDCLIB_INT_MAX +#define PRId16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, d ) ) +#define PRIi16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, i ) ) +#define PRIo16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, o ) ) +#define PRIu16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, u ) ) +#define PRIx16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, x ) ) +#define PRIX16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, X ) ) +#endif +#define SCNd16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, d ) ) +#define SCNi16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, i ) ) +#define SCNo16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, o ) ) +#define SCNu16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, u ) ) +#define SCNx16 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST16_PREFIX, x ) ) +#endif + +#if _PDCLIB_INT_LEAST32_MAX == 0x7fffffffl +#if _PDCLIB_INT_LEAST32_MAX > _PDCLIB_INT_MAX +#define PRId32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, d ) ) +#define PRIi32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, i ) ) +#define PRIo32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, o ) ) +#define PRIu32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, u ) ) +#define PRIx32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, x ) ) +#define PRIX32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, X ) ) +#endif +#define SCNd32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, d ) ) +#define SCNi32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, i ) ) +#define SCNo32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, o ) ) +#define SCNu32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, u ) ) +#define SCNx32 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST32_PREFIX, x ) ) +#endif + +#if _PDCLIB_INT_LEAST64_MAX == 0x7fffffffffffffffll +#if _PDCLIB_INT_LEAST64_MAX > _PDCLIB_INT_MAX +#define PRId64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, d ) ) +#define PRIi64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, i ) ) +#define PRIo64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, o ) ) +#define PRIu64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, u ) ) +#define PRIx64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, x ) ) +#define PRIX64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, X ) ) +#endif +#define SCNd64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, d ) ) +#define SCNi64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, i ) ) +#define SCNo64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, o ) ) +#define SCNu64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, u ) ) +#define SCNx64 _PDCLIB_value2string( _PDCLIB_concat( _PDCLIB_INT_LEAST64_PREFIX, x ) ) +#endif + +#endif + +/* 7.8.2 Functions for greatest-width integer types */ + +/* Calculate the absolute value of j */ +_PDCLIB_PUBLIC intmax_t imaxabs( intmax_t j ); + +/* Return quotient (quot) and remainder (rem) of an integer division in the + imaxdiv_t struct. +*/ +_PDCLIB_PUBLIC imaxdiv_t imaxdiv( intmax_t numer, intmax_t denom ); + +/* Separate the character array nptr into three parts: A (possibly empty) + sequence of whitespace characters, a character representation of an integer + to the given base, and trailing invalid characters (including the terminating + null character). If base is 0, assume it to be 10, unless the integer + representation starts with 0x / 0X (setting base to 16) or 0 (setting base to + 8). If given, base can be anything from 0 to 36, using the 26 letters of the + base alphabet (both lowercase and uppercase) as digits 10 through 35. + The integer representation is then converted into the return type of the + function. It can start with a '+' or '-' sign. If the sign is '-', the result + of the conversion is negated. + If the conversion is successful, the converted value is returned. If endptr + is not a NULL pointer, a pointer to the first trailing invalid character is + returned in *endptr. + If no conversion could be performed, zero is returned (and nptr in *endptr, + if endptr is not a NULL pointer). If the converted value does not fit into + the return type, the functions return INTMAX_MIN, INTMAX_MAX, or UINTMAX_MAX, + respectively, depending on the sign of the integer representation and the + return type, and errno is set to ERANGE. +*/ +/* This function is equivalent to strtol() / strtoul() in , but on + the potentially larger type. +*/ +_PDCLIB_PUBLIC intmax_t strtoimax( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); +_PDCLIB_PUBLIC uintmax_t strtoumax( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); + +/* TODO: wcstoimax(), wcstoumax() */ + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_INTTYPES_H +#include _PDCLIB_EXTEND_INTTYPES_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/iso646.h b/libc/include/iso646.h new file mode 100644 index 0000000..88e521c --- /dev/null +++ b/libc/include/iso646.h @@ -0,0 +1,31 @@ +/* Alternative spellings + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_ISO646_H +#define _PDCLIB_ISO646_H _PDCLIB_ISO646_H + +#ifndef __cplusplus +#define and && +#define and_eq &= +#define bitand & +#define bitor | +#define compl ~ +#define not ! +#define not_eq != +#define or || +#define or_eq |= +#define xor ^ +#define xor_eq ^= +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_ISO646_H +#include _PDCLIB_EXTEND_ISO646_H +#endif + +#endif diff --git a/libc/include/limits.h b/libc/include/limits.h new file mode 100644 index 0000000..72268a4 --- /dev/null +++ b/libc/include/limits.h @@ -0,0 +1,50 @@ +/* Sizes of integer types + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_LIMITS_H +#define _PDCLIB_LIMITS_H _PDCLIB_LIMITS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +/* TODO: Defined to 1 as multibyte characters are not supported yet. */ +#define MB_LEN_MAX 1 + +#define LLONG_MIN _PDCLIB_LLONG_MIN +#define LLONG_MAX _PDCLIB_LLONG_MAX +#define ULLONG_MAX _PDCLIB_ULLONG_MAX + +#define CHAR_BIT _PDCLIB_CHAR_BIT +#define CHAR_MAX _PDCLIB_CHAR_MAX +#define CHAR_MIN _PDCLIB_CHAR_MIN +#define SCHAR_MAX _PDCLIB_SCHAR_MAX +#define SCHAR_MIN _PDCLIB_SCHAR_MIN +#define UCHAR_MAX _PDCLIB_UCHAR_MAX +#define SHRT_MAX _PDCLIB_SHRT_MAX +#define SHRT_MIN _PDCLIB_SHRT_MIN +#define INT_MAX _PDCLIB_INT_MAX +#define INT_MIN _PDCLIB_INT_MIN +#define LONG_MAX _PDCLIB_LONG_MAX +#define LONG_MIN _PDCLIB_LONG_MIN +#define USHRT_MAX _PDCLIB_USHRT_MAX +#define UINT_MAX _PDCLIB_UINT_MAX +#define ULONG_MAX _PDCLIB_ULONG_MAX + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_LIMITS_H +#include _PDCLIB_EXTEND_LIMITS_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/locale.h b/libc/include/locale.h new file mode 100644 index 0000000..0c18446 --- /dev/null +++ b/libc/include/locale.h @@ -0,0 +1,114 @@ +/* Localization + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_LOCALE_H +#define _PDCLIB_LOCALE_H _PDCLIB_LOCALE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +/* The structure returned by localeconv(). + + The values for *_sep_by_space: + 0 - no space + 1 - if symbol and sign are adjacent, a space separates them from the value; + otherwise a space separates the symbol from the value + 2 - if symbol and sign are adjacent, a space separates them; otherwise a + space separates the sign from the value + + The values for *_sign_posn: + 0 - Parentheses surround value and symbol + 1 - sign precedes value and symbol + 2 - sign succeeds value and symbol + 3 - sign immediately precedes symbol + 4 - sign immediately succeeds symbol +*/ +struct lconv +{ + char * decimal_point; /* decimal point character */ /* LC_NUMERIC */ + char * thousands_sep; /* character for separating groups of digits */ /* LC_NUMERIC */ + char * grouping; /* string indicating the size of digit groups */ /* LC_NUMERIC */ + char * mon_decimal_point; /* decimal point for monetary quantities */ /* LC_MONETARY */ + char * mon_thousands_sep; /* thousands_sep for monetary quantities */ /* LC_MONETARY */ + char * mon_grouping; /* grouping for monetary quantities */ /* LC_MONETARY */ + char * positive_sign; /* string indicating nonnegative mty. qty. */ /* LC_MONETARY */ + char * negative_sign; /* string indicating negative mty. qty. */ /* LC_MONETARY */ + char * currency_symbol; /* local currency symbol (e.g. '$') */ /* LC_MONETARY */ + char * int_curr_symbol; /* international currency symbol (e.g. "USD" */ /* LC_MONETARY */ + char frac_digits; /* fractional digits in local monetary qty. */ /* LC_MONETARY */ + char p_cs_precedes; /* if currency_symbol precedes positive qty. */ /* LC_MONETARY */ + char n_cs_precedes; /* if currency_symbol precedes negative qty. */ /* LC_MONETARY */ + char p_sep_by_space; /* if it is separated by space from pos. qty. */ /* LC_MONETARY */ + char n_sep_by_space; /* if it is separated by space from neg. qty. */ /* LC_MONETARY */ + char p_sign_posn; /* positioning of positive_sign for mon. qty. */ /* LC_MONETARY */ + char n_sign_posn; /* positioning of negative_sign for mon. qty. */ /* LC_MONETARY */ + char int_frac_digits; /* Same as above, for international format */ /* LC_MONETARY */ + char int_p_cs_precedes; /* Same as above, for international format */ /* LC_MONETARY */ + char int_n_cs_precedes; /* Same as above, for international format */ /* LC_MONETARY */ + char int_p_sep_by_space; /* Same as above, for international format */ /* LC_MONETARY */ + char int_n_sep_by_space; /* Same as above, for international format */ /* LC_MONETARY */ + char int_p_sign_posn; /* Same as above, for international format */ /* LC_MONETARY */ + char int_n_sign_posn; /* Same as above, for international format */ /* LC_MONETARY */ +}; + +/* First arguments to setlocale(). + NOTE: If you add to / modify these, look at functions/locale/setlocale.c + and keep things in sync. +*/ +/* Entire locale */ +#define LC_ALL _PDCLIB_LC_ALL +/* Collation (strcoll(), strxfrm()) */ +#define LC_COLLATE _PDCLIB_LC_COLLATE +/* Character types (, ) */ +#define LC_CTYPE _PDCLIB_LC_CTYPE +/* Monetary formatting (as returned by localeconv) */ +#define LC_MONETARY _PDCLIB_LC_MONETARY +/* Decimal-point character (for printf() / scanf() functions), string + conversions, nonmonetary formatting as returned by localeconv +*/ +#define LC_NUMERIC _PDCLIB_LC_NUMERIC +/* Time formats (strftime(), wcsftime()) */ +#define LC_TIME _PDCLIB_LC_TIME +/* Messages (not specified but allowed by C99, and specified by POSIX) + (used by perror() / strerror()) +*/ +#define LC_MESSAGES _PDCLIB_LC_MESSAGES + +/* The category parameter can be any of the LC_* macros to specify if the call + to setlocale() shall affect the entire locale or only a portion thereof. + The category locale specifies which locale should be switched to, with "C" + being the minimal default locale, and "" being the locale-specific native + environment. A NULL pointer makes setlocale() return the *current* setting. + Otherwise, returns a pointer to a string associated with the specified + category for the new locale. +*/ +_PDCLIB_PUBLIC char * setlocale( int category, const char * locale ); + +/* Returns a struct lconv initialized to the values appropriate for the current + locale setting. +*/ +_PDCLIB_PUBLIC struct lconv * localeconv( void ); + +#ifdef __cplusplus +} +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_LOCALE_H +#include _PDCLIB_EXTEND_LOCALE_H +#endif + +#endif diff --git a/libc/include/pdclib/_PDCLIB_config.h b/libc/include/pdclib/_PDCLIB_config.h new file mode 100644 index 0000000..0660098 --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_config.h @@ -0,0 +1,904 @@ +/* Internal PDCLib configuration <_PDCLIB_config.h> + ("Example" platform target, for PDCLib development) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_CONFIG_H +#define _PDCLIB_CONFIG_H _PDCLIB_CONFIG_H + +/* -------------------------------------------------------------------------- */ +/* Misc */ +/* -------------------------------------------------------------------------- */ + +/* Helper macros also documented in _PDCLIB_internal.h, but defined here as */ +/* they are needed in this file already. */ +/* _PDCLIB_cc( x, y ) concatenates two preprocessor tokens without extending. */ +/* _PDCLIB_concat( x, y ) concatenates two preprocessor tokens with extending */ +#define _PDCLIB_cc( x, y ) x ## y +#define _PDCLIB_concat( x, y ) _PDCLIB_cc( x, y ) + +/* exit() can signal success to the host environment by the value of zero or */ +/* the constant EXIT_SUCCESS. Failure is signaled by EXIT_FAILURE. Note that */ +/* any other return value is "implementation-defined", i.e. your environment */ +/* is not required to handle it gracefully. Set your definitions here. */ +#define _PDCLIB_SUCCESS 0 +#define _PDCLIB_FAILURE -1 + +/* qsort() in requires a function that swaps two memory areas. */ +/* Below is a naive implementation that can be improved significantly for */ +/* specific platforms, e.g. by swapping int instead of char. */ +#define _PDCLIB_memswp( i, j, size ) \ + char tmp; \ + do { \ + tmp = *i; \ + *i++ = *j; \ + *j++ = tmp; \ + } while ( --size ); + +/* Define this to some compiler directive that can be written after the */ +/* parameter list of a function declaration to indicate the function does */ +/* never return. If your compiler does not support such a directive, define */ +/* to nothing. (This is to avoid warnings with the exit functions under GCC */ +/* when compiling with C99/C++ settings, where C11 _Noreturn is unavailable.) */ +#define _PDCLIB_NORETURN __attribute__(( noreturn )) + +/* -------------------------------------------------------------------------- */ +/* Symbol Visibility */ +/* -------------------------------------------------------------------------- */ + +/* This defines _PDCLIB_PUBLIC to indicate external linkage, and _PDCLIB_LOCAL + to indicate local linkage. +*/ + +#ifdef _PDCLIB_STATIC_DEFINE + #define _PDCLIB_PUBLIC + #define _PDCLIB_LOCAL +#else + #if defined _WIN32 || defined __CYGWIN__ + #ifdef _PDCLIB_BUILD + #ifdef __GNUC__ + #define _PDCLIB_PUBLIC __attribute__ ((dllexport)) + #else + #define _PDCLIB_PUBLIC __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define _PDCLIB_PUBLIC __attribute__ ((dllimport)) + #else + #define _PDCLIB_PUBLIC __declspec(dllimport) + #endif + #endif + #define _PDCLIB_LOCAL + #else + #if __GNUC__ >= 4 + #define _PDCLIB_PUBLIC __attribute__ ((visibility ("default"))) + #define _PDCLIB_LOCAL __attribute__ ((visibility ("hidden"))) + #else + #define _PDCLIB_PUBLIC + #define _PDCLIB_LOCAL + #endif + #endif +#endif + +/* -------------------------------------------------------------------------- */ +/* Integers */ +/* -------------------------------------------------------------------------- */ +/* The defines below make use of predefines offered by GCC and clang. If you */ +/* adapt PDCLib for a different compiler family, you will have to use what */ +/* that compiler provides, or enter actual values. */ +/* -------------------------------------------------------------------------- */ + +/* At the point of writing, PDCLib makes no provisions for, nor has it been */ +/* tested, on a platform that uses signed magnitude or one's complement to */ +/* encode its integers. Most importantly, there are no guarantees that the */ +/* negative zero of those encodings is in any form handled gracefully. */ +#define _PDCLIB_TWOS_COMPLEMENT 1 + +/* 1234 for little endian, 4321 for big endian; other types not supported. */ +#define _PDCLIB_ENDIANESS __BYTE_ORDER__ + +/* Calculation of a minimum value from a given maximum for two's complement. */ +/* (For convenience only, used only in this header file below.) */ +#define _PDCLIB_MIN_CALC( max ) ( ( - max ) - 1 ) + +/* Now, introducting the various predefines to the _PDCLIB_* namespace, so */ +/* the rest of PDCLib can work with that and adapting to a different compiler */ +/* will require changes only in this one file. */ + +/* Bits in a char */ +#define _PDCLIB_CHAR_BIT __CHAR_BIT__ + +/* Maximum and minimum values of signed / unsigned char */ +#define _PDCLIB_SCHAR_MAX __SCHAR_MAX__ +#define _PDCLIB_SCHAR_MIN _PDCLIB_MIN_CALC( __SCHAR_MAX__ ) +#define _PDCLIB_UCHAR_MAX ( __SCHAR_MAX__ * 2 + 1 ) + +/* Whether the 'char' type is unsigned */ +#ifdef __CHAR_UNSIGNED__ +#define _PDCLIB_CHAR_MAX _PDCLIB_UCHAR_MAX +#define _PDCLIB_CHAR_MIN 0 +#else +#define _PDCLIB_CHAR_MAX _PDCLIB_SCHAR_MAX +#define _PDCLIB_CHAR_MIN _PDCLIB_SCHAR_MIN +#endif + +/* Maximum and minimum values of signed / unsigned short */ +#define _PDCLIB_SHRT_MAX __SHRT_MAX__ +#define _PDCLIB_SHRT_MIN _PDCLIB_MIN_CALC( __SHRT_MAX__ ) +#define _PDCLIB_USHRT_MAX ( __SHRT_MAX__ * 2u + 1 ) + +/* Maximum and minimum values of signed / unsigned int */ +#define _PDCLIB_INT_MAX __INT_MAX__ +#define _PDCLIB_INT_MIN _PDCLIB_MIN_CALC( __INT_MAX__ ) +#define _PDCLIB_UINT_MAX ( __INT_MAX__ * 2u + 1 ) + +/* Maximum and minimum values of signed / unsigned long */ +#define _PDCLIB_LONG_MAX __LONG_MAX__ +#define _PDCLIB_LONG_MIN _PDCLIB_MIN_CALC( __LONG_MAX__ ) +#define _PDCLIB_ULONG_MAX ( __LONG_MAX__ * 2ul + 1 ) + +/* Maximum and minimum values of signed / unsigned long long */ +#define _PDCLIB_LLONG_MAX __LONG_LONG_MAX__ +#define _PDCLIB_LLONG_MIN _PDCLIB_MIN_CALC( __LONG_LONG_MAX__ ) +#define _PDCLIB_ULLONG_MAX ( __LONG_LONG_MAX__ * 2ull + 1 ) + +/* -------------------------------------------------------------------------- */ +/* defines a set of integer types that are of a minimum width, and */ +/* "usually fastest" on the system. (If, for example, accessing a single char */ +/* requires the CPU to access a complete int and then mask out the char, the */ +/* "usually fastest" type of at least 8 bits would be int, not char.) */ +/* If you do not have information on the relative performance of the types, */ +/* the standard allows you to define any type that meets minimum width and */ +/* signedness requirements. */ +/* The first define is the appropriate basic type (e.g. "long int"), second */ +/* its max value, the third its min value (both expressed in the given type). */ +/* The same follows for the unsigned type (for which the minimum value is */ +/* obviously zero and need not be defined). */ +/* There *are* predefines provided for the printf()/scanf() length specifiers */ +/* but tunneling them through here would have added many lines of repetitive */ +/* and mostly redundant defines. They are determined in <_PDCLIB_internal.h>. */ +/* -------------------------------------------------------------------------- */ + +/* int_fast8_t / uint_fast8_t */ +#define _PDCLIB_int_fast8_t __INT_FAST8_TYPE__ +#define _PDCLIB_INT_FAST8_MAX __INT_FAST8_MAX__ +#define _PDCLIB_INT_FAST8_MIN _PDCLIB_MIN_CALC( __INT_FAST8_MAX__ ) +#define _PDCLIB_uint_fast8_t __UINT_FAST8_TYPE__ +#define _PDCLIB_UINT_FAST8_MAX __UINT_FAST8_MAX__ + +/* int_least8_t / uint_least8_t */ +#define _PDCLIB_int_least8_t __INT_LEAST8_TYPE__ +#define _PDCLIB_INT_LEAST8_MAX __INT_LEAST8_MAX__ +#define _PDCLIB_INT_LEAST8_MIN _PDCLIB_MIN_CALC( __INT_LEAST8_MAX__ ) +#define _PDCLIB_uint_least8_t __UINT_LEAST8_TYPE__ +#define _PDCLIB_UINT_LEAST8_MAX __UINT_LEAST8_MAX__ + +/* int_fast16_t / uint_fast16_t */ +#define _PDCLIB_int_fast16_t __INT_FAST16_TYPE__ +#define _PDCLIB_INT_FAST16_MAX __INT_FAST16_MAX__ +#define _PDCLIB_INT_FAST16_MIN _PDCLIB_MIN_CALC( __INT_FAST16_MAX__ ) +#define _PDCLIB_uint_fast16_t __UINT_FAST16_TYPE__ +#define _PDCLIB_UINT_FAST16_MAX __UINT_FAST16_MAX__ + +/* int_least16_t / uint_least16_t */ +#define _PDCLIB_int_least16_t __INT_LEAST16_TYPE__ +#define _PDCLIB_INT_LEAST16_MAX __INT_LEAST16_MAX__ +#define _PDCLIB_INT_LEAST16_MIN _PDCLIB_MIN_CALC( __INT_LEAST16_MAX__ ) +#define _PDCLIB_uint_least16_t __UINT_LEAST16_TYPE__ +#define _PDCLIB_UINT_LEAST16_MAX __UINT_LEAST16_MAX__ + +/* int_fast32_t / uint_fast32_t */ +#define _PDCLIB_int_fast32_t __INT_FAST32_TYPE__ +#define _PDCLIB_INT_FAST32_MAX __INT_FAST32_MAX__ +#define _PDCLIB_INT_FAST32_MIN _PDCLIB_MIN_CALC( __INT_FAST32_MAX__ ) +#define _PDCLIB_uint_fast32_t __UINT_FAST32_TYPE__ +#define _PDCLIB_UINT_FAST32_MAX __UINT_FAST32_MAX__ + +/* int_least32_t / uint_least32_t */ +#define _PDCLIB_int_least32_t __INT_LEAST32_TYPE__ +#define _PDCLIB_INT_LEAST32_MAX __INT_LEAST32_MAX__ +#define _PDCLIB_INT_LEAST32_MIN _PDCLIB_MIN_CALC( __INT_LEAST32_MAX__ ) +#define _PDCLIB_uint_least32_t __UINT_LEAST32_TYPE__ +#define _PDCLIB_UINT_LEAST32_MAX __UINT_LEAST32_MAX__ + +/* int_fast64_t / uint_fast64_t */ +#define _PDCLIB_int_fast64_t __INT_FAST64_TYPE__ +#define _PDCLIB_INT_FAST64_MAX __INT_FAST64_MAX__ +#define _PDCLIB_INT_FAST64_MIN _PDCLIB_MIN_CALC( __INT_FAST64_MAX__ ) +#define _PDCLIB_uint_fast64_t __UINT_FAST64_TYPE__ +#define _PDCLIB_UINT_FAST64_MAX __UINT_FAST64_MAX__ + +/* int_least64_t / uint_least64_t */ +#define _PDCLIB_int_least64_t __INT_LEAST64_TYPE__ +#define _PDCLIB_INT_LEAST64_MAX __INT_LEAST64_MAX__ +#define _PDCLIB_INT_LEAST64_MIN _PDCLIB_MIN_CALC( __INT_LEAST64_MAX__ ) +#define _PDCLIB_uint_least64_t __UINT_LEAST64_TYPE__ +#define _PDCLIB_UINT_LEAST64_MAX __UINT_LEAST64_MAX__ + +/* Exact-width integer types. These are *optional*. If your platform does not */ +/* support types of these exact widths in two's complement encoding, just */ +/* leave them undefined. */ +#define _PDCLIB_int8_t __INT8_TYPE__ +#define _PDCLIB_int16_t __INT16_TYPE__ +#define _PDCLIB_int32_t __INT32_TYPE__ +#define _PDCLIB_int64_t __INT64_TYPE__ +#define _PDCLIB_uint8_t __UINT8_TYPE__ +#define _PDCLIB_uint16_t __UINT16_TYPE__ +#define _PDCLIB_uint32_t __UINT32_TYPE__ +#define _PDCLIB_uint64_t __UINT64_TYPE__ + +/* INTn_C / UINTn_C macros to define int_leastN_t / uint_leastN_t literals. */ +#if defined( __INT8_C ) +/* GCC */ +#define _PDCLIB_INT_LEAST8_C __INT8_C +#define _PDCLIB_UINT_LEAST8_C __UINT8_C +#define _PDCLIB_INT_LEAST16_C __INT16_C +#define _PDCLIB_UINT_LEAST16_C __UINT16_C +#define _PDCLIB_INT_LEAST32_C __INT32_C +#define _PDCLIB_UINT_LEAST32_C __UINT32_C +#define _PDCLIB_INT_LEAST64_C __INT64_C +#define _PDCLIB_UINT_LEAST64_C __UINT64_C +#elif defined( __INT8_C_SUFFIX__ ) +/* Clang */ +#define _PDCLIB_INT_LEAST8_C(c) _PDCLIB_concat( c, __INT8_C_SUFFIX__ ) +#define _PDCLIB_UINT_LEAST8_C(c) _PDCLIB_concat( c, __UINT8_C_SUFFIX__ ) +#define _PDCLIB_INT_LEAST16_C(c) _PDCLIB_concat( c, __INT16_C_SUFFIX__ ) +#define _PDCLIB_UINT_LEAST16_C(c) _PDCLIB_concat( c, __UINT16_C_SUFFIX__ ) +#define _PDCLIB_INT_LEAST32_C(c) _PDCLIB_concat( c, __INT32_C_SUFFIX__ ) +#define _PDCLIB_UINT_LEAST32_C(c) _PDCLIB_concat( c, __UINT32_C_SUFFIX__ ) +#define _PDCLIB_INT_LEAST64_C(c) _PDCLIB_concat( c, __INT64_C_SUFFIX__ ) +#define _PDCLIB_UINT_LEAST64_C(c) _PDCLIB_concat( c, __UINT64_C_SUFFIX__ ) +#else +#error Please create your own _PDCLIB_config.h. Using the existing one as-is will not work. (Unsupported *INTn_C macros.) +#endif + +/* defines the div() function family that allows taking quotient */ +/* and remainder of an integer division in one operation. Many platforms */ +/* support this in hardware / opcode, and the standard permits ordering of */ +/* the return structure in any way to fit the hardware. That is why those */ +/* structs can be configured here. */ + +struct _PDCLIB_div_t +{ + int quot; + int rem; +}; + +struct _PDCLIB_ldiv_t +{ + long int quot; + long int rem; +}; + +struct _PDCLIB_lldiv_t +{ + long long int quot; + long long int rem; +}; + +/* -------------------------------------------------------------------------- */ +/* What follows are a couple of "special" typedefs and their limits. */ +/* -------------------------------------------------------------------------- */ + +/* The result type of substracting two pointers */ +#define _PDCLIB_ptrdiff_t __PTRDIFF_TYPE__ +#define _PDCLIB_PTRDIFF_MAX __PTRDIFF_MAX__ +#define _PDCLIB_PTRDIFF_MIN _PDCLIB_MIN_CALC( __PTRDIFF_MAX__ ) + +/* An integer type that can be accessed as atomic entity (think asynchronous */ +/* interrupts). In a freestanding environment, the type itself need not be */ +/* defined, but its limits must. (Don't ask.) GCC is so kind to predefine it, */ +/* but clang is only giving us its MAX value, so we use that to identify the */ +/* type in _PDCLIB_int.h if the type definition is unavailable. */ +#ifdef __SIG_ATOMIC_TYPE__ +#define _PDCLIB_sig_atomic_t __SIG_ATOMIC_TYPE__ +#endif +#define _PDCLIB_SIG_ATOMIC_MAX __SIG_ATOMIC_MAX__ +#define _PDCLIB_SIG_ATOMIC_MIN _PDCLIB_MIN_CALC( __SIG_ATOMIC_MAX__ ) + +/* Result type of the 'sizeof' operator (must be unsigned). */ +/* Note: In , this is taken as the base for RSIZE_MAX, the limit */ +/* for the bounds-checking interfaces of Annex K. The recommendation by the */ +/* standard is to use ( SIZE_MAX >> 1 ) when "targeting machines with large */ +/* addess spaces", whereas small address spaces should use SIZE_MAX directly. */ +#define _PDCLIB_size_t __SIZE_TYPE__ +#define _PDCLIB_SIZE_MAX __SIZE_MAX__ + +/* Large enough an integer to hold all character codes of the widest */ +/* supported locale. */ +#define _PDCLIB_wchar_t __WCHAR_TYPE__ +#define _PDCLIB_WCHAR_MAX __WCHAR_MAX__ +#define _PDCLIB_WCHAR_MIN __WCHAR_MIN__ + +/* Large enough an integer to hold all character codes of the widest */ +/* supported locale plus WEOF (which needs not to be equal to EOF, nor needs */ +/* to be of negative value). */ +#define _PDCLIB_wint_t __WINT_TYPE__ +#define _PDCLIB_WINT_MAX __WINT_MAX__ +#define _PDCLIB_WINT_MIN __WINT_MIN__ + +/* Integer types capable of taking the (cast) value of a void *, and having */ +/* the value cast back to void *, comparing equal to the original. */ +#define _PDCLIB_intptr_t __INTPTR_TYPE__ +#define _PDCLIB_INTPTR_MAX __INTPTR_MAX__ +#define _PDCLIB_INTPTR_MIN _PDCLIB_MIN_CALC( __INTPTR_MAX__ ) +#define _PDCLIB_uintptr_t __UINTPTR_TYPE__ +#define _PDCLIB_UINTPTR_MAX __UINTPTR_MAX__ + +/* Largest supported integer type. Implementation note: see _PDCLIB_atomax(). */ +#define _PDCLIB_intmax_t __INTMAX_TYPE__ +#define _PDCLIB_INTMAX_MAX __INTMAX_MAX__ +#define _PDCLIB_INTMAX_MIN _PDCLIB_MIN_CALC( __INTMAX_MAX__ ) +#define _PDCLIB_INTMAX_C __INTMAX_C +#define _PDCLIB_uintmax_t __UINTMAX_TYPE__ +#define _PDCLIB_UINTMAX_MAX __UINTMAX_MAX__ +#define _PDCLIB_UINTMAX_C __UINTMAX_C + +/* defines imaxdiv(), which is equivalent to the div() function */ +/* family (see further above) with intmax_t as basis. */ +struct _PDCLIB_imaxdiv_t +{ + _PDCLIB_intmax_t quot; + _PDCLIB_intmax_t rem; +}; + +/* -------------------------------------------------------------------------- */ +/* Time types, limits, constants, and paths */ +/* -------------------------------------------------------------------------- */ + +/* _PDCLIB_time is the type for type_t; _PDCLIB_clock for clock_t. Both types */ +/* are defined as "real types capable of representing times". The "range and */ +/* precision of times representable" is implementation-defined. */ + +/* For clock_t, the standard defines that dividing the result of clock() by */ +/* CLOCKS_PER_SEC gives the seconds elapsed. */ +#ifdef __CYGWIN__ +#define _PDCLIB_clock_t unsigned long +#else +#define _PDCLIB_clock_t long +#endif +#define _PDCLIB_CLOCKS_PER_SEC 1000000 + +/* For time_t, no such divider exists. Most implementations use a count of */ +/* seconds since a specified epoch. While PDCLib really should support other */ +/* encodings as well, for now "count of seconds" is the only supported one. */ +/* MIN / MAX values for time_t are not required by the standard (and they are */ +/* not "exported" from the _PDCLIB namespace), but they are useful in support */ +/* of the _tzcode implementation. */ +#ifdef __MINGW64__ +#define _PDCLIB_time_t long long +#define _PDCLIB_TIME_MAX __LONG_LONG_MAX__ +#define _PDCLIB_TIME_MIN _PDCLIB_MIN_CALC( __LONG_LONG_MAX__ ) +#else +#define _PDCLIB_time_t long +#define _PDCLIB_TIME_MAX __LONG_MAX__ +#define _PDCLIB_TIME_MIN _PDCLIB_MIN_CALC( __LONG_MAX__ ) +#endif + +/* "Unix time" uses 1970-01-01T00:00:00 as "epoch". If your system uses a */ +/* different "zero point" for its timestamps, set this to the offset between */ +/* your epoch and Unix epoch. (For example, NTP uses 1900-01-01T00:00:00 as */ +/* epoch, giving an offset of (70 * 365 + 17) * 86400 = 220898800 seconds.) */ +#define _PDCLIB_EPOCH_BIAS INT64_C( 0 ) + +/* Leave this alone for now. */ +#define _PDCLIB_TIME_UTC 1 + +/* Path to TZ data. */ +/* IMPORTANT: *Must* end with separator character! */ +/* It does make it much easier for the time data handling code if this detail */ +/* can be relied upon and need not be handled in code. */ +#define _PDCLIB_TZDIR "/usr/share/zoneinfo/" + +/* Path to default (local) timezone */ +#define _PDCLIB_TZDEFAULT "/etc/localtime" + +/* -------------------------------------------------------------------------- */ +/* Floating Point */ +/* -------------------------------------------------------------------------- */ + +/* Whether the implementation rounds toward zero (0), to nearest (1), toward */ +/* positive infinity (2), or toward negative infinity (3). (-1) signifies */ +/* indeterminable rounding, any other value implementation-specific rounding. */ +#define _PDCLIB_FLT_ROUNDS -1 + +/* Check for explanations on each of these values. */ +#define _PDCLIB_FLT_EVAL_METHOD __FLT_EVAL_METHOD__ + +#define _PDCLIB_FLT_HAS_SUBNORM __FLT_HAS_DENORM__ +#define _PDCLIB_DBL_HAS_SUBNORM __DBL_HAS_DENORM__ +#define _PDCLIB_LDBL_HAS_SUBNORM __LDBL_HAS_DENORM__ + +#define _PDCLIB_FLT_RADIX __FLT_RADIX__ + +#define _PDCLIB_FLT_MANT_DIG __FLT_MANT_DIG__ +#define _PDCLIB_DBL_MANT_DIG __DBL_MANT_DIG__ +#define _PDCLIB_LDBL_MANT_DIG __LDBL_MANT_DIG__ + +#define _PDCLIB_FLT_DECIMAL_DIG __FLT_DECIMAL_DIG__ +#define _PDCLIB_DBL_DECIMAL_DIG __DBL_DECIMAL_DIG__ +#define _PDCLIB_LDBL_DECIMAL_DIG __LDBL_DECIMAL_DIG__ + +#define _PDCLIB_DECIMAL_DIG __DECIMAL_DIG__ + +#define _PDCLIB_FLT_DIG __FLT_DIG__ +#define _PDCLIB_DBL_DIG __DBL_DIG__ +#define _PDCLIB_LDBL_DIG __LDBL_DIG__ + +#define _PDCLIB_FLT_MIN_EXP __FLT_MIN_EXP__ +#define _PDCLIB_DBL_MIN_EXP __DBL_MIN_EXP__ +#define _PDCLIB_LDBL_MIN_EXP __LDBL_MIN_EXP__ + +#define _PDCLIB_FLT_MIN_10_EXP __FLT_MIN_10_EXP__ +#define _PDCLIB_DBL_MIN_10_EXP __DBL_MIN_10_EXP__ +#define _PDCLIB_LDBL_MIN_10_EXP __LDBL_MIN_10_EXP__ + +#define _PDCLIB_FLT_MAX_EXP __FLT_MAX_EXP__ +#define _PDCLIB_DBL_MAX_EXP __DBL_MAX_EXP__ +#define _PDCLIB_LDBL_MAX_EXP __LDBL_MAX_EXP__ + +#define _PDCLIB_FLT_MAX_10_EXP __FLT_MAX_10_EXP__ +#define _PDCLIB_DBL_MAX_10_EXP __DBL_MAX_10_EXP__ +#define _PDCLIB_LDBL_MAX_10_EXP __LDBL_MAX_10_EXP__ + +#define _PDCLIB_FLT_MAX __FLT_MAX__ +#define _PDCLIB_DBL_MAX __DBL_MAX__ +#define _PDCLIB_LDBL_MAX __LDBL_MAX__ + +#define _PDCLIB_FLT_EPSILON __FLT_EPSILON__ +#define _PDCLIB_DBL_EPSILON __DBL_EPSILON__ +#define _PDCLIB_LDBL_EPSILON __LDBL_EPSILON__ + +#define _PDCLIB_FLT_MIN __FLT_MIN__ +#define _PDCLIB_DBL_MIN __DBL_MIN__ +#define _PDCLIB_LDBL_MIN __LDBL_MIN__ + +#define _PDCLIB_FLT_TRUE_MIN __FLT_DENORM_MIN__ +#define _PDCLIB_DBL_TRUE_MIN __DBL_DENORM_MIN__ +#define _PDCLIB_LDBL_TRUE_MIN __LDBL_DENORM_MIN__ + +/* Macros for deconstructing floating point values */ +#define _PDCLIB_DBL_SIGN( bytes ) ( ( (unsigned)bytes[7] & 0x80 ) >> 7 ) +#define _PDCLIB_DBL_DEC( bytes ) ( ( _PDCLIB_DBL_EXP( bytes ) > 0 ) ? 1 : 0 ) +#define _PDCLIB_DBL_EXP( bytes ) ( ( ( (unsigned)bytes[7] & 0x7f ) << 4 ) | ( ( (unsigned)bytes[6] & 0xf0 ) >> 4 ) ) +#define _PDCLIB_DBL_BIAS 1023 +#define _PDCLIB_DBL_MANT_START( bytes ) ( bytes + 6 ) + +/* Most platforms today use IEEE 754 single precision for 'float', and double */ +/* precision for 'double'. But type 'long double' varies. We use what the */ +/* compiler states about LDBL_MANT_DIG to determine the type. */ +#if _PDCLIB_LDBL_MANT_DIG == 64 + +/* Intel "Extended Precision" format, using 80 bits (64bit mantissa) */ +#define _PDCLIB_LDBL_SIGN( bytes ) ( ( (unsigned)bytes[9] & 0x80 ) >> 7 ) +#define _PDCLIB_LDBL_DEC( bytes ) ( ( (unsigned)bytes[7] & 0x80 ) >> 7 ) +#define _PDCLIB_LDBL_EXP( bytes ) ( ( ( (unsigned)bytes[9] & 0x7f ) << 8 ) | (unsigned)bytes[8] ) +#define _PDCLIB_LDBL_BIAS 16383 +#define _PDCLIB_LDBL_MANT_START( bytes ) ( bytes + 7 ) + +#elif _PDCLIB_LDBL_MANT_DIG == 113 + +/* IEEE "Quadruple Precision" format, using 128 bits (113bit mantissa) */ +#define _PDCLIB_LDBL_SIGN( bytes ) ( ( (unsigned)bytes[15] & 0x80 ) >> 7 ) +#define _PDCLIB_LDBL_DEC( bytes ) ( ( _PDCLIB_LDBL_EXP( bytes ) > 0 ) ? 1 : 0 ) +#define _PDCLIB_LDBL_EXP( bytes ) ( ( ( (unsigned)bytes[15] & 0x7f ) << 8 ) | (unsigned)bytes[14] ) +#define _PDCLIB_LDBL_BIAS 16383 +#define _PDCLIB_LDBL_MANT_START( bytes ) ( bytes + 13 ) + +#else + +/* IEEE "Double Precision" format, using 64 bits (53bit mantissa, + same as DBL above) */ +#define _PDCLIB_LDBL_SIGN( bytes ) ( ( (unsigned)bytes[7] & 0x80 ) >> 7 ) +#define _PDCLIB_LDBL_DEC( bytes ) ( ( _PDCLIB_LDBL_EXP( bytes ) > 0 ) ? 1 : 0 ) +#define _PDCLIB_LDBL_EXP( bytes ) ( ( ( (unsigned)bytes[7] & 0x7f ) << 4 ) | ( ( (unsigned)bytes[6] & 0xf0 ) >> 4 ) ) +#define _PDCLIB_LDBL_BIAS 1023 +#define _PDCLIB_LDBL_MANT_START( bytes ) ( bytes + 6 ) + +#endif + +/* -------------------------------------------------------------------------- */ +/* Big Integer Arithmetic */ +/* -------------------------------------------------------------------------- */ +/* In support of the floating point converstions required by printf() etc., */ +/* PDCLib provides rudimentary big integer arithmetics. The _PDCLIB_bigint_t */ +/* type stores values in a sequence of integer "digits", which may be of any */ +/* uint_leastN_t type with N being 32 or 16. Note that multiplication and */ +/* division require the help of the next larger type. So set the define to */ +/* 32 if efficient 64bit integer arithmetics are available on your platform, */ +/* and to 16 otherwise. */ +/* (The value range of _PDCLIB_bigint_t is not affected by this setting.) */ + +#define _PDCLIB_BIGINT_DIGIT_BITS 16 + +/* -------------------------------------------------------------------------- */ +/* Platform-dependent macros defined by the standard headers. */ +/* -------------------------------------------------------------------------- */ + +/* The offsetof macro */ +/* Contract: Expand to an integer constant expression of type size_t, which */ +/* represents the offset in bytes to the structure member from the beginning */ +/* of the structure. If the specified member is a bitfield, behaviour is */ +/* undefined. */ +/* There is no standard-compliant way to do this. */ +/* This implementation casts an integer zero to 'pointer to type', and then */ +/* takes the address of member. This is undefined behaviour but should work */ +/* on most compilers. */ +#define _PDCLIB_offsetof( type, member ) ( (size_t) &( ( (type *) 0 )->member ) ) + +/* Variable Length Parameter List Handling () */ +/* The macros defined by are highly dependent on the calling */ +/* conventions used, and you probably have to replace them with builtins of */ +/* your compiler. */ + +#if defined( __i386 ) + +/* The following generic implementation works only for pure stack-based */ +/* architectures, and only if arguments are aligned to pointer type. Credits */ +/* to Michael Moody, who contributed this to the Public Domain. */ + +/* Internal helper macro. va_round is not part of . */ +#define _PDCLIB_va_round( type ) ( (sizeof(type) + sizeof(void *) - 1) & ~(sizeof(void *) - 1) ) + +typedef char * _PDCLIB_va_list; +#define _PDCLIB_va_arg( ap, type ) ( (ap) += (_PDCLIB_va_round(type)), ( *(type*) ( (ap) - (_PDCLIB_va_round(type)) ) ) ) +#define _PDCLIB_va_copy( dest, src ) ( (dest) = (src), (void)0 ) +#define _PDCLIB_va_end( ap ) ( (ap) = (void *)0, (void)0 ) +#define _PDCLIB_va_start( ap, parmN ) ( (ap) = (char *) &parmN + ( _PDCLIB_va_round(parmN) ), (void)0 ) + +#elif defined( __x86_64 ) || defined( __arm__ ) || defined( __ARM_NEON ) + +/* No way to cover x86_64 or arm with a generic implementation, as it uses */ +/* register-based parameter passing. Using compiler builtins here. */ +typedef __builtin_va_list _PDCLIB_va_list; +#define _PDCLIB_va_arg( ap, type ) ( __builtin_va_arg( ap, type ) ) +#define _PDCLIB_va_copy( dest, src ) ( __builtin_va_copy( dest, src ) ) +#define _PDCLIB_va_end( ap ) ( __builtin_va_end( ap ) ) +#define _PDCLIB_va_start( ap, parmN ) ( __builtin_va_start( ap, parmN ) ) + +#else + +#error Please create your own _PDCLIB_config.h. Using the existing one as-is will not work. (Unsupported varargs.) + +#endif + +/* -------------------------------------------------------------------------- */ +/* OS "glue", part 1 */ +/* These are values and data type definitions that you would have to adapt to */ +/* the capabilities and requirements of your OS. */ +/* The actual *functions* of the OS interface are declared in _PDCLIB_glue.h. */ +/* -------------------------------------------------------------------------- */ + +/* I/O ---------------------------------------------------------------------- */ + +/* The type of the file descriptor returned by _PDCLIB_open(), i.e. whatever */ +/* the underlying kernel uses for stream identification. */ +typedef int _PDCLIB_fd_t; + +/* The value of type _PDCLIB_fd_t returned by _PDCLIB_open() if the operation */ +/* failed. */ +#define _PDCLIB_NOHANDLE ( (_PDCLIB_fd_t) -1 ) + +/* The default size for file buffers. Must be at least 256. */ +#define _PDCLIB_BUFSIZ 1024 + +/* The minimum number of files the implementation guarantees can opened */ +/* simultaneously. Must be at least 8. Depends largely on how the platform */ +/* does the bookkeeping in whatever is called by _PDCLIB_open(). PDCLib puts */ +/* no further limits on the number of open files other than available memory. */ +#define _PDCLIB_FOPEN_MAX 8 + +/* Length of the longest filename the implementation guarantees to support. */ +#define _PDCLIB_FILENAME_MAX 128 + +/* Maximum length of filenames generated by tmpnam(). (See tmpfile.c.) */ +#define _PDCLIB_L_tmpnam 46 + +/* Number of distinct file names that can be generated by tmpnam(). */ +#define _PDCLIB_TMP_MAX 50 + +/* The values of SEEK_SET, SEEK_CUR and SEEK_END, used by fseek(). */ +/* Since at least one platform (POSIX) uses the same symbols for its own */ +/* "seek" function, you should use whatever the host defines (if it does */ +/* define them). */ +#define _PDCLIB_SEEK_SET 0 +#define _PDCLIB_SEEK_CUR 1 +#define _PDCLIB_SEEK_END 2 + +/* The number of characters that can be buffered with ungetc(). The standard */ +/* guarantees only one (1); PDCLib supports larger values, but applications */ +/* relying on this would rely on implementation-defined behaviour (not good). */ +#define _PDCLIB_UNGETCBUFSIZE 1 + +/* The number of functions that can be registered with atexit(). Needs to be */ +/* at least 33 (32 guaranteed by the standard, plus _PDCLIB_closeall() which */ +/* is used internally by PDCLib to close all open streams). */ +/* TODO: Should expand dynamically. */ +#define _PDCLIB_ATEXIT_SLOTS 40 + +/* errno -------------------------------------------------------------------- */ + +/* These are the values that _PDCLIB_errno can be set to by the library. */ +/* */ +/* By keeping PDCLib's errno in the _PDCLIB_* namespace, the library is */ +/* capable of "translating" between errno values used by the hosting OS and */ +/* those used and passed out by the library. */ +/* */ +/* Example: In the example platform, the remove() function uses the unlink() */ +/* system call as backend. Linux sets its errno to EISDIR if you try to */ +/* unlink() a directory, but POSIX demands EPERM. Within the remove() */ +/* function, you can catch 'errno == EISDIR', and set '*_PDCLIB_errno_func() */ +/* = _PDCLIB_EPERM'. Anyone using PDCLib's will "see" EPERM instead */ +/* of EISDIR. */ +/* */ +/* If you do not want that kind of translation, you might want to "match" the */ +/* values used by PDCLib with those used by the host OS, to avoid confusion. */ +/* auxiliary/errno/errno_readout.c provides a convenience program to read */ +/* those errno values mandated by the standard from a platform's , */ +/* giving output that can readily be pasted here. */ +/* Either way, note that the list below, the list in PDCLib's , and */ +/* the list in _PDCLIB_stdinit.h, need to be kept in sync. */ +/* */ +/* The values below are read from a Linux system. */ + +/* Argument list too long */ +#define _PDCLIB_E2BIG 7 +/* Permission denied */ +#define _PDCLIB_EACCES 13 +/* Address in use */ +#define _PDCLIB_EADDRINUSE 98 +/* Address not available */ +#define _PDCLIB_EADDRNOTAVAIL 99 +/* Address family not supported */ +#define _PDCLIB_EAFNOSUPPORT 97 +/* Resource unavailable, try again */ +#define _PDCLIB_EAGAIN 11 +/* Connection already in progress */ +#define _PDCLIB_EALREADY 114 +/* Bad file descriptor */ +#define _PDCLIB_EBADF 9 +/* Bad message */ +#define _PDCLIB_EBADMSG 74 +/* Device or resource busy */ +#define _PDCLIB_EBUSY 16 +/* Operation canceled */ +#define _PDCLIB_ECANCELED 125 +/* No child processes */ +#define _PDCLIB_ECHILD 10 +/* Connection aborted */ +#define _PDCLIB_ECONNABORTED 103 +/* Connection refused */ +#define _PDCLIB_ECONNREFUSED 111 +/* Connection reset */ +#define _PDCLIB_ECONNRESET 104 +/* Resource deadlock would occur */ +#define _PDCLIB_EDEADLK 35 +/* Destination address required */ +#define _PDCLIB_EDESTADDRREQ 89 +/* Mathematics argument out of domain of function */ +#define _PDCLIB_EDOM 33 +/* File exists */ +#define _PDCLIB_EEXIST 17 +/* Bad address */ +#define _PDCLIB_EFAULT 14 +/* File too large */ +#define _PDCLIB_EFBIG 27 +/* Host is unreachable */ +#define _PDCLIB_EHOSTUNREACH 113 +/* Identifier removed */ +#define _PDCLIB_EIDRM 43 +/* Illegal byte sequence */ +#define _PDCLIB_EILSEQ 84 +/* Operation in progress */ +#define _PDCLIB_EINPROGRESS 115 +/* Interrupted function */ +#define _PDCLIB_EINTR 4 +/* Invalid argument */ +#define _PDCLIB_EINVAL 22 +/* I/O error */ +#define _PDCLIB_EIO 5 +/* Socket is connected */ +#define _PDCLIB_EISCONN 106 +/* Is a directory */ +#define _PDCLIB_EISDIR 21 +/* Too many levels of symbolic links */ +#define _PDCLIB_ELOOP 40 +/* File descriptor value too large */ +#define _PDCLIB_EMFILE 24 +/* Too many links */ +#define _PDCLIB_EMLINK 31 +/* Message too large */ +#define _PDCLIB_EMSGSIZE 90 +/* Filename too long */ +#define _PDCLIB_ENAMETOOLONG 36 +/* Network is down */ +#define _PDCLIB_ENETDOWN 100 +/* Connection aborted by network */ +#define _PDCLIB_ENETRESET 102 +/* Network unreachable */ +#define _PDCLIB_ENETUNREACH 101 +/* Too many files open in system */ +#define _PDCLIB_ENFILE 23 +/* No buffer space available */ +#define _PDCLIB_ENOBUFS 105 +/* No message is available on the STREAM head read queue */ +#define _PDCLIB_ENODATA 61 +/* No such device */ +#define _PDCLIB_ENODEV 19 +/* No such file or directory */ +#define _PDCLIB_ENOENT 2 +/* Executable file format error */ +#define _PDCLIB_ENOEXEC 8 +/* No locks available */ +#define _PDCLIB_ENOLCK 37 +/* Link has been severed */ +#define _PDCLIB_ENOLINK 67 +/* Not enough space */ +#define _PDCLIB_ENOMEM 12 +/* No message of the desired type */ +#define _PDCLIB_ENOMSG 42 +/* Protocol not available */ +#define _PDCLIB_ENOPROTOOPT 92 +/* No space left on device */ +#define _PDCLIB_ENOSPC 28 +/* No STREAM resources */ +#define _PDCLIB_ENOSR 63 +/* Not a STREAM */ +#define _PDCLIB_ENOSTR 60 +/* Function not supported */ +#define _PDCLIB_ENOSYS 38 +/* The socket is not connected */ +#define _PDCLIB_ENOTCONN 107 +/* Not a directory */ +#define _PDCLIB_ENOTDIR 20 +/* Directory not empty */ +#define _PDCLIB_ENOTEMPTY 39 +/* State not recoverable */ +#define _PDCLIB_ENOTRECOVERABLE 131 +/* Not a socket */ +#define _PDCLIB_ENOTSOCK 88 +/* Not supported */ +#define _PDCLIB_ENOTSUP 95 +/* Inappropriate I/O control operation */ +#define _PDCLIB_ENOTTY 25 +/* No such device or address */ +#define _PDCLIB_ENXIO 6 +/* Operation not supported on socket */ +#define _PDCLIB_EOPNOTSUPP 95 +/* Value too large to be stored in data type */ +#define _PDCLIB_EOVERFLOW 75 +/* Previous owner died */ +#define _PDCLIB_EOWNERDEAD 130 +/* Operation not permitted */ +#define _PDCLIB_EPERM 1 +/* Broken pipe */ +#define _PDCLIB_EPIPE 32 +/* Protocol error */ +#define _PDCLIB_EPROTO 71 +/* Protocol not supported */ +#define _PDCLIB_EPROTONOSUPPORT 93 +/* Protocol wrong type for socket */ +#define _PDCLIB_EPROTOTYPE 91 +/* Result too large */ +#define _PDCLIB_ERANGE 34 +/* Read-only file system */ +#define _PDCLIB_EROFS 30 +/* Invalid seek */ +#define _PDCLIB_ESPIPE 29 +/* No such process */ +#define _PDCLIB_ESRCH 3 +/* Stream ioctl() timeout */ +#define _PDCLIB_ETIME 62 +/* Connection timed out */ +#define _PDCLIB_ETIMEDOUT 110 +/* Text file busy */ +#define _PDCLIB_ETXTBSY 26 +/* Operation would block */ +#define _PDCLIB_EWOULDBLOCK 11 +/* Cross-device link */ +#define _PDCLIB_EXDEV 18 + +/* The highest defined errno value, plus one. This is used to set the size */ +/* of the array in struct _PDCLIB_lc_text_t holding error messages for the */ +/* strerror() and perror() functions. (If you change this value because you */ +/* are using additional errno values, you *HAVE* to provide appropriate error */ +/* messages for *ALL* locales.) */ +#define _PDCLIB_ERRNO_MAX 132 + +/* The error message used for unknown error codes (generated by errno_readout */ +/* for consistency between the 'holes' in the list of defined error messages */ +/* and the text generated by e.g. strerror() for out-of-range error values.) */ +#define _PDCLIB_EUNKNOWN_TEXT (char*)"unknown error" + +/* locale data -------------------------------------------------------------- */ + +/* The default path where PDCLib should look for its locale data. */ +/* Must end with the appropriate separator character. */ +#define _PDCLIB_LOCALE_PATH "/usr/share/pdclib/i18n/" + +/* The name of the environment variable that can be used to override that */ +/* path setting. */ +#define _PDCLIB_LOCALE_PATH_ENV PDCLIB_I18N + +#ifdef __CYGWIN__ +typedef unsigned int wint_t; +#endif + +/* threads ------------------------------------------------------------------ */ + +/* This is relying on underlying implementation to provide thread */ +/* support. */ +/* The problem here is we cannot just #include and access the */ +/* original definitions. The standard library must not drag identifiers into */ +/* the user's namespace, so we have to set our own definitions in the _PDCLIB */ +/* namespace. Which are, obviously, platform-specific. */ +/* If you do NOT want to provide threads support, define __STDC_NO_THREADS__ */ +/* to 1 and simply delete the threads.h header and the corresponding files in */ +/* functions/threads/. This makes PDCLib non-thread-safe (obviously), as the */ +/* safeguards against race conditions (e.g. in ) will be omitted. */ + +/* auxiliary/pthread/pthread_readout.c provides a convenience program to read */ +/* appropriate definitions from a platform's , giving output that */ +/* can be copy & pasted here. */ + +typedef unsigned long int _PDCLIB_thrd_t; +typedef union { unsigned char _PDCLIB_cnd_t_data[ 48 ]; long long int _PDCLIB_cnd_t_align; } _PDCLIB_cnd_t; +#if defined( __arm__ ) || defined( __ARM_NEON ) +typedef union { unsigned char _PDCLIB_mtx_t_data[ 24 ]; long int _PDCLIB_mtx_t_align; } _PDCLIB_mtx_t; +#else +typedef union { unsigned char _PDCLIB_mtx_t_data[ 40 ]; long int _PDCLIB_mtx_t_align; } _PDCLIB_mtx_t; +#endif +typedef unsigned int _PDCLIB_tss_t; +typedef int _PDCLIB_once_flag; +#define _PDCLIB_ONCE_FLAG_INIT 0 +#define _PDCLIB_RECURSIVE_MUTEX_INIT PTHREAD_MUTEX_INITIALIZER +/* This one is actually hidden in , and only if __USE_POSIX is */ +/* defined prior to #include (PTHREAD_DESTRUCTOR_ITERATIONS). */ +#define _PDCLIB_TSS_DTOR_ITERATIONS 4 +/* The following are not made public in any header, but used internally for */ +/* interfacing with the pthread API. */ +typedef union { unsigned char _PDCLIB_cnd_attr_t_data[ 4 ]; int _PDCLIB_cnd_attr_t_align; } _PDCLIB_cnd_attr_t; +typedef union { unsigned char _PDCLIB_mtx_attr_t_data[ 4 ]; int _PDCLIB_mtx_attr_t_align; } _PDCLIB_mtx_attr_t; +#if defined( __arm__ ) || defined( __ARM_NEON ) +typedef union { unsigned char _PDCLIB_thrd_attr_t_data[ 36 ]; long int _PDCLIB_thrd_attr_t_align; } _PDCLIB_thrd_attr_t; +#else +typedef union { unsigned char _PDCLIB_thrd_attr_t_data[ 56 ]; long int _PDCLIB_thrd_attr_t_align; } _PDCLIB_thrd_attr_t; +#endif +/* Static initialization of recursive mutex. */ +#if defined( __arm__ ) || defined( __ARM_NEON ) +#define _PDCLIB_MTX_RECURSIVE_INIT { {\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } +/* Static initialization of plain / timeout mutex (identical with pthread). */ +#define _PDCLIB_MTX_PLAIN_INIT { {\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } +#else +#define _PDCLIB_MTX_RECURSIVE_INIT { {\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } +/* Static initialization of plain / timeout mutex (identical with pthread). */ +#define _PDCLIB_MTX_PLAIN_INIT { {\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } +#endif + +/* Termux defines atexit in crtbegin_so.o leading to a multiple definition */ +/* error from the linker. This is a crude workaround, which does NOT fix */ +/* various run-time issues on Termux likely also related to crt linkage. But */ +/* at least things compile OK, and SOME tests can be run. */ +#if defined( __ARM_NEON ) +#define atexit _PDCLIB_atexit +#endif + +#endif diff --git a/libc/include/pdclib/_PDCLIB_defguard.h b/libc/include/pdclib/_PDCLIB_defguard.h new file mode 100644 index 0000000..cd51abe --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_defguard.h @@ -0,0 +1,29 @@ +/* Definition guard <_PDCLIB_defguard.h> + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_DEFGUARD_H +#define _PDCLIB_DEFGUARD_H _PDCLIB_DEFGUARD_H + +#if defined( __ANDROID__ ) +/* typedef sigset_t */ +#include "bits/signal_types.h" +#endif + +/* Linux defines its own version of struct timespec (from ) in + some internal header (depending on clib implementation), which leads + to problems when accessing e.g. sys/time.h (type redefinition). + The solution is to set the Linux header's include guard (to avoid + Linux' definition), and to include PDCLib's to define the + type unambiguously. +*/ + +#define _TIMESPEC_DEFINED +#define _SYS__TIMESPEC_H_ +#define _STRUCT_TIMESPEC + +#include + +#endif diff --git a/libc/include/pdclib/_PDCLIB_glue.h b/libc/include/pdclib/_PDCLIB_glue.h new file mode 100644 index 0000000..3e59302 --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_glue.h @@ -0,0 +1,93 @@ +/* OS glue functions declaration <_PDCLIB_glue.h> + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_GLUE_H +#define _PDCLIB_GLUE_H _PDCLIB_GLUE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +/* -------------------------------------------------------------------------- */ +/* OS "glue", part 2 */ +/* These are the functions you will have to touch, as they are where PDCLib */ +/* interfaces with the operating system. */ +/* They operate on data types partially defined by _PDCLIB_config.h. */ +/* -------------------------------------------------------------------------- */ + +/* stdlib.h */ + +/* A system call that terminates the calling process, returning a given status + to the environment. +*/ +_PDCLIB_LOCAL _PDCLIB_Noreturn void _PDCLIB_Exit( int status ) _PDCLIB_NORETURN; + + +/* stdio.h */ + +/* A system call that opens a file identified by name in a given mode. Return + a file descriptor uniquely identifying that file. + (The mode is the return value of the _PDCLIB_filemode() function.) +*/ +_PDCLIB_LOCAL _PDCLIB_fd_t _PDCLIB_open( const char * const filename, unsigned int mode ); + +/* A system call that writes a stream's buffer. + Returns 0 on success, EOF on write error. + Sets stream error flags and errno appropriately on error. +*/ +_PDCLIB_LOCAL int _PDCLIB_flushbuffer( struct _PDCLIB_file_t * stream ); + +/* A system call that fills a stream's buffer. + Returns 0 on success, EOF on read error / EOF. + Sets stream EOF / error flags and errno appropriately on error. +*/ +_PDCLIB_LOCAL int _PDCLIB_fillbuffer( struct _PDCLIB_file_t * stream ); + +/* A system call that repositions within a file. Returns new offset on success, + -1 / errno on error. +*/ +_PDCLIB_LOCAL _PDCLIB_int_least64_t _PDCLIB_seek( struct _PDCLIB_file_t * stream, _PDCLIB_int_least64_t offset, int whence ); + +/* A system call that closes a file identified by given file descriptor. Return + zero on success, non-zero otherwise. +*/ +_PDCLIB_LOCAL int _PDCLIB_close( _PDCLIB_fd_t fd ); + +/* A system call that changes the mode of a given stream to that passed as + argument (the argument being the value returned by _PDCLIB_filemode()), + *without* closing the stream. See comments in example implementation + for details. Return zero if the requested mode change is not supported + for this stream and freopen() should try to close and reopen the stream; + return INT_MIN if the change is not supported and freopen() should close + and NOT try to close / reopen (i.e., fail). Return any other value on + success. +*/ +_PDCLIB_LOCAL int _PDCLIB_changemode( struct _PDCLIB_file_t * stream, unsigned int mode ); + +/* A system call that returns a canonicalized absolute filename in + dynamically allocated memory, or NULL if the file does not exist. +*/ +_PDCLIB_LOCAL char * _PDCLIB_realpath( const char * path ); + +/* A system call that removes a file. Return zero on success, non-zero + otherwise. +*/ +_PDCLIB_LOCAL int _PDCLIB_remove( const char * pathname ); + +/* A system call that renames a file from given old name to given new name. + Return zero on success, non-zero otherwise. In case of failure, the file + must still be accessible by old name. Any handling of open files etc. is + done by standard rename() already. +*/ +_PDCLIB_LOCAL int _PDCLIB_rename( const char * oldpath, const char * newpath ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/pdclib/_PDCLIB_internal.h b/libc/include/pdclib/_PDCLIB_internal.h new file mode 100644 index 0000000..20b1ae0 --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_internal.h @@ -0,0 +1,776 @@ +/* PDCLib internal logic <_PDCLIB_internal.h> + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_INTERNAL_H +#define _PDCLIB_INTERNAL_H _PDCLIB_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* -------------------------------------------------------------------------- */ +/* You should not have to edit anything in this file; if you DO have to, it */ +/* would be considered a bug / missing feature: notify the author(s). */ +/* -------------------------------------------------------------------------- */ + +#include "pdclib/_PDCLIB_config.h" + +/* -------------------------------------------------------------------------- */ +/* Standard Version */ +/* -------------------------------------------------------------------------- */ + +/* Many a compiler gets this wrong, so you might have to hardcode it instead. */ + +#if __STDC__ != 1 +#error Compiler does not define _ _STDC_ _ to 1 (not standard-compliant)! +#endif + +#ifndef __STDC_HOSTED__ +#error Compiler does not define _ _STDC_HOSTED_ _ (not standard-compliant)! +#elif __STDC_HOSTED__ != 0 && __STDC_HOSTED__ != 1 +#error Compiler does not define _ _STDC_HOSTED_ _ to 0 or 1 (not standard-compliant)! +#endif + +/* null pointer constant -- ((void *)0) in C, 0 in C++98, nullptr since C++11 */ +#ifdef __cplusplus +#if __cplusplus >= 201103L +#define _PDCLIB_NULL nullptr +#else +#define _PDCLIB_NULL 0 +#endif +#else +#define _PDCLIB_NULL ((void *)0) +#endif + +/* restrict / inline enabled for C99 onward only */ +#if defined( __cplusplus ) || ! defined( __STDC_VERSION ) || __STDC_VERSION__ < 199901L +#define _PDCLIB_restrict +#define _PDCLIB_inline +#else +#define _PDCLIB_restrict restrict +#define _PDCLIB_inline inline +#endif + +/* noreturn enabled for C11 onward only */ +#if defined( __cplusplus ) && __cplusplus >= 201103L +#define _PDCLIB_Noreturn [[noreturn]] +#else +#if defined( __STDC_VERSION__ ) >= 201112L +#define _PDCLIB_Noreturn _Noreturn +#else +#define _PDCLIB_Noreturn +#endif +#endif + +/* -------------------------------------------------------------------------- */ +/* Helper macros: */ +/* */ +/* (defined in _PDCLIB_config.h) */ +/* _PDCLIB_cc( x, y ) concatenates two preprocessor tokens without extending. */ +/* _PDCLIB_concat( x, y ) concatenates two preprocessor tokens with extending */ +/* */ +/* (defined below) */ +/* _PDCLIB_static_assert( e, m ) does a compile-time assertion of expression */ +/* e, with m as the failure message. */ +/* _PDCLIB_symbol2string( x ) turn symbol into string literal (by adding ""). */ +/* _PDCLIB_value2string( x ) expands a preprocessor token and turns it into a */ +/* string literal (by adding ""). */ +/* _PDCLIB_TYPE_SIGNED( type ) resolves to true if type is signed. */ +/* _PDCLIB_LOCK( mtx ) lock a mutex if library has threads support. */ +/* _PDCLIB_UNLOCK( mtx ) unlock a mutex if library has threads support. */ +/* _PDCLIB_CONSTRAINT_VIOLATION( e ) expand errno number e to parameter list */ +/* fit for Annex K constraint violation */ +/* handler. */ +/* -------------------------------------------------------------------------- */ + +#define _PDCLIB_static_assert( e, m ) enum { _PDCLIB_concat( _PDCLIB_assert_, __LINE__ ) = 1 / ( !!(e) ) } + +#define _PDCLIB_TYPE_SIGNED( type ) (((type) -1) < 0) + +#define _PDCLIB_symbol2string( x ) #x +#define _PDCLIB_value2string( x ) _PDCLIB_symbol2string( x ) + +#ifndef __STDC_NO_THREADS__ +#define _PDCLIB_LOCK( mtx ) mtx_lock( &mtx ) +#define _PDCLIB_UNLOCK( mtx ) mtx_unlock( &mtx ) +#else +#define _PDCLIB_LOCK( mtx ) +#define _PDCLIB_UNLOCK( mtx ) +#endif + +#define _PDCLIB_CONSTRAINT_VIOLATION( e ) _PDCLIB_lc_messages->errno_texts[e], NULL, e + +#define _PDCLIB_GETC( fh ) ( ( fh->ungetidx == 0 ) ? ( unsigned char )fh->buffer[ fh->bufidx++ ] : ( unsigned char )fh->ungetbuf[ --fh->ungetidx ] ) + +#define _PDCLIB_CHECKBUFFER( fh ) ( ( ( fh->bufidx == fh->bufend ) && ( fh->ungetidx == 0 ) ) ? _PDCLIB_fillbuffer( fh ) : 0 ) + +/* -------------------------------------------------------------------------- */ +/* Preparing the length modifiers used in . */ +/* -------------------------------------------------------------------------- */ + +/* We use the _MAX value as a proxy for the actual type here. That is crude + but the best we can do, cross-platform wise. + Identifying which type the leastN_t / fastN_t / intmax_t / intptr_t are + and providing the appropriate printf()/scanf() length modifier. +*/ + +#if _PDCLIB_INT_FAST8_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_FAST8_PREFIX hh +#elif _PDCLIB_INT_FAST8_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_FAST8_PREFIX h +#elif _PDCLIB_INT_FAST8_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_FAST8_PREFIX +#elif _PDCLIB_INT_FAST8_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_FAST8_PREFIX l +#elif _PDCLIB_INT_FAST8_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_FAST8_PREFIX ll +#else +#error No matching native type for int_fast8_t. Please check your setup. +#endif + +#if _PDCLIB_INT_FAST16_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_FAST16_PREFIX hh +#elif _PDCLIB_INT_FAST16_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_FAST16_PREFIX h +#elif _PDCLIB_INT_FAST16_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_FAST16_PREFIX +#elif _PDCLIB_INT_FAST16_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_FAST16_PREFIX l +#elif _PDCLIB_INT_FAST16_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_FAST16_PREFIX ll +#else +#error No matching native type for int_fast16_t. Please check your setup. +#endif + +#if _PDCLIB_INT_FAST32_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_FAST32_PREFIX hh +#elif _PDCLIB_INT_FAST32_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_FAST32_PREFIX h +#elif _PDCLIB_INT_FAST32_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_FAST32_PREFIX +#elif _PDCLIB_INT_FAST32_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_FAST32_PREFIX l +#elif _PDCLIB_INT_FAST32_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_FAST32_PREFIX ll +#else +#error No matching native type for int_fast32_t. Please check your setup. +#endif + +#if _PDCLIB_INT_FAST64_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_FAST64_PREFIX hh +#elif _PDCLIB_INT_FAST64_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_FAST64_PREFIX h +#elif _PDCLIB_INT_FAST64_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_FAST64_PREFIX +#elif _PDCLIB_INT_FAST64_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_FAST64_PREFIX l +#elif _PDCLIB_INT_FAST64_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_FAST64_PREFIX ll +#else +#error No matching native type for int_fast64_t. Please check your setup. +#endif + +/* Many of the combinations below can very likely be ruled out logically. + All combinations are still listed for simplicity's sake (and to not fall + into the trap of false assumptions). +*/ + +#if _PDCLIB_INT_LEAST8_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_LEAST8_PREFIX hh +#elif _PDCLIB_INT_LEAST8_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_LEAST8_PREFIX h +#elif _PDCLIB_INT_LEAST8_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_LEAST8_PREFIX +#elif _PDCLIB_INT_LEAST8_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_LEAST8_PREFIX l +#elif _PDCLIB_INT_LEAST8_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_LEAST8_PREFIX ll +#else +#error No matching native type for int_least8_t. Please check your setup. +#endif + +#if _PDCLIB_INT_LEAST16_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_LEAST16_PREFIX hh +#elif _PDCLIB_INT_LEAST16_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_LEAST16_PREFIX h +#elif _PDCLIB_INT_LEAST16_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_LEAST16_PREFIX +#elif _PDCLIB_INT_LEAST16_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_LEAST16_PREFIX l +#elif _PDCLIB_INT_LEAST16_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_LEAST16_PREFIX ll +#else +#error No matching native type for int_least16_t. Please check your setup. +#endif + +#if _PDCLIB_INT_LEAST32_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_LEAST32_PREFIX hh +#elif _PDCLIB_INT_LEAST32_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_LEAST32_PREFIX h +#elif _PDCLIB_INT_LEAST32_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_LEAST32_PREFIX +#elif _PDCLIB_INT_LEAST32_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_LEAST32_PREFIX l +#elif _PDCLIB_INT_LEAST32_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_LEAST32_PREFIX ll +#else +#error No matching native type for int_least32_t. Please check your setup. +#endif + +#if _PDCLIB_INT_LEAST64_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INT_LEAST64_PREFIX hh +#elif _PDCLIB_INT_LEAST64_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INT_LEAST64_PREFIX h +#elif _PDCLIB_INT_LEAST64_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INT_LEAST64_PREFIX +#elif _PDCLIB_INT_LEAST64_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INT_LEAST64_PREFIX l +#elif _PDCLIB_INT_LEAST64_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INT_LEAST64_PREFIX ll +#else +#error No matching native type for int_least64_t. Please check your setup. +#endif + +#if _PDCLIB_INTMAX_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INTMAX_PREFIX hh +#elif _PDCLIB_INTMAX_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INTMAX_PREFIX h +#elif _PDCLIB_INTMAX_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INTMAX_PREFIX +#elif _PDCLIB_INTMAX_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INTMAX_PREFIX l +#elif _PDCLIB_INTMAX_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INTMAX_PREFIX ll +#else +#error No matching native type for intmax_t. Please check your setup. +#endif + +#if _PDCLIB_INTPTR_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_INTPTR_PREFIX hh +#elif _PDCLIB_INTPTR_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_INTPTR_PREFIX h +#elif _PDCLIB_INTPTR_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_INTPTR_PREFIX +#elif _PDCLIB_INTPTR_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_INTPTR_PREFIX l +#elif _PDCLIB_INTPTR_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_INTPTR_PREFIX ll +#else +#error No matching native type for intptr_t. Please check your setup. +#endif + +/* We might not have a type definition for sig_atomic_t at this point. The */ +/* clang compiler does not provide an appropriate predefine for it. So if we */ +/* do not have _PDCLIB_sig_atomic_t, identify the type trough its MAX value. */ + +#ifndef _PDCLIB_sig_atomic_t + +#if _PDCLIB_SIG_ATOMIC_MAX == _PDCLIB_SCHAR_MAX +#define _PDCLIB_sig_atomic_t char +#elif _PDCLIB_SIG_ATOMIC_MAX == _PDCLIB_SHRT_MAX +#define _PDCLIB_sig_atomic_t short +#elif _PDCLIB_SIG_ATOMIC_MAX == _PDCLIB_INT_MAX +#define _PDCLIB_sig_atomic_t int +#elif _PDCLIB_SIG_ATOMIC_MAX == _PDCLIB_LONG_MAX +#define _PDCLIB_sig_atomic_t long +#elif _PDCLIB_SIG_ATOMIC_MAX == _PDCLIB_LLONG_MAX +#define _PDCLIB_sig_atomic_t long long +#else +#error No matching native type for sig_atomic_t. Please check your setup. +#endif + +#endif + +/* -------------------------------------------------------------------------- */ +/* Various internals */ +/* -------------------------------------------------------------------------- */ + +/* Flags for representing mode (see fopen()). Note these must fit the same + status field as the _IO?BF flags in and the internal flags below. +*/ +#define _PDCLIB_FREAD (1u<<3) +#define _PDCLIB_FWRITE (1u<<4) +#define _PDCLIB_FAPPEND (1u<<5) +#define _PDCLIB_FRW (1u<<6) +#define _PDCLIB_FBIN (1u<<7) + +/* Internal flags, made to fit the same status field as the flags above. */ +/* -------------------------------------------------------------------------- */ +/* free() the buffer memory on closing (setvbuf()) */ +#define _PDCLIB_FREEBUFFER (1u<<8) +/* stream has encountered error / EOF */ +#define _PDCLIB_ERRORFLAG (1u<<9) +#define _PDCLIB_EOFFLAG (1u<<10) +/* stream is wide-oriented */ +#define _PDCLIB_WIDESTREAM (1u<<11) +/* stream is byte-oriented */ +#define _PDCLIB_BYTESTREAM (1u<<12) +/* file associated with stream should be remove()d on closing (tmpfile()) */ +#define _PDCLIB_DELONCLOSE (1u<<13) + +/* Position / status structure for getpos() / fsetpos(). */ +struct _PDCLIB_fpos_t +{ + _PDCLIB_uint_least64_t offset; /* File position offset */ + int status; /* Multibyte parsing state (unused, reserved) */ +}; + +/* FILE structure */ +struct _PDCLIB_file_t +{ + _PDCLIB_fd_t handle; /* OS file handle */ + char * buffer; /* Pointer to buffer memory */ + _PDCLIB_size_t bufsize; /* Size of buffer */ + _PDCLIB_size_t bufidx; /* Index of current position in buffer */ + _PDCLIB_size_t bufend; /* Index of last pre-read character in buffer */ + struct _PDCLIB_fpos_t pos; /* Offset and multibyte parsing state */ + _PDCLIB_size_t ungetidx; /* Number of ungetc()'ed characters */ + unsigned char ungetbuf[_PDCLIB_UNGETCBUFSIZE]; /* ungetc() buffer */ + unsigned int status; /* Status flags; see above */ + /* multibyte parsing status to be added later */ +#ifndef __STDC_NO_THREADS__ + _PDCLIB_mtx_t mtx; /* Multithreading safety */ +#endif + char * filename; /* Name the current stream has been opened with */ + struct _PDCLIB_file_t * next; /* Pointer to next struct (internal) */ +}; + +/* -------------------------------------------------------------------------- */ +/* Internal data types */ +/* -------------------------------------------------------------------------- */ + +/* Structure required by both atexit() and exit() for handling atexit functions */ +struct _PDCLIB_exitfunc_t +{ + struct _PDCLIB_exitfunc_t * next; + void ( *func )( void ); +}; + +/* Status structure required by _PDCLIB_print(). */ +struct _PDCLIB_status_t +{ + int base; /* base to which the value shall be converted */ + _PDCLIB_int_fast32_t flags; /* flags and length modifiers */ + _PDCLIB_size_t n; /* print: maximum characters to be written */ + /* scan: number matched conversion specifiers */ + _PDCLIB_size_t i; /* number of characters read/written */ + _PDCLIB_size_t current;/* chars read/written in the CURRENT conversion */ + char * s; /* *sprintf(): target buffer */ + /* *sscanf(): source string */ + _PDCLIB_size_t width; /* specified field width */ + int prec; /* specified field precision */ + struct _PDCLIB_file_t * stream; /* *fprintf() / *fscanf() stream */ + _PDCLIB_va_list arg; /* argument stack */ +}; + +/* -------------------------------------------------------------------------- */ +/* Declaration of helper functions (implemented in functions/_PDCLIB). */ +/* -------------------------------------------------------------------------- */ + +/* This is the main function called by atoi(), atol() and atoll(). */ +_PDCLIB_LOCAL _PDCLIB_intmax_t _PDCLIB_atomax( const char * s ); + +/* Two helper functions used by strtol(), strtoul() and long long variants. */ +_PDCLIB_LOCAL const char * _PDCLIB_strtox_prelim( const char * p, char * sign, int * base ); +_PDCLIB_LOCAL _PDCLIB_uintmax_t _PDCLIB_strtox_main( const char ** p, unsigned int base, _PDCLIB_uintmax_t error, _PDCLIB_uintmax_t limval, int limdigit, char * sign ); + +/* A helper function used by strtof(), strtod(), and strtold(). */ +_PDCLIB_LOCAL void _PDCLIB_strtod_scan( const char * s, const char ** dec, const char ** frac, const char ** exp, int base ); + +/* Digits arrays used by various integer conversion functions */ +extern const char _PDCLIB_digits[]; +extern const char _PDCLIB_Xdigits[]; + +/* The worker for all printf() type of functions. The pointer spec should point + to the introducing '%' of a conversion specifier. The status structure is to + be that of the current printf() function, of which the members n, s, stream + and arg will be preserved; i will be updated; and all others will be trashed + by the function. + Returns a pointer to the first character not parsed as conversion specifier. +*/ +_PDCLIB_LOCAL const char * _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status ); + +/* The worker for all scanf() type of functions. The pointer spec should point + to the introducing '%' of a conversion specifier. The status structure is to + be that of the current scanf() function, of which the member stream will be + preserved; n, i, and s will be updated; and all others will be trashed by + the function. + Returns a pointer to the first character not parsed as conversion specifier, + or NULL in case of error. + FIXME: Should distinguish between matching and input error +*/ +_PDCLIB_LOCAL const char * _PDCLIB_scan( const char * spec, struct _PDCLIB_status_t * status ); + +/* Parsing any fopen() style filemode string into a number of flags. */ +_PDCLIB_LOCAL unsigned int _PDCLIB_filemode( const char * mode ); + +/* Initialize a FILE structure. If the parameter is NULL, a new FILE structure + is malloc'ed. Returns a pointer to the stream if successful, NULL otherwise. +*/ +_PDCLIB_LOCAL struct _PDCLIB_file_t * _PDCLIB_init_file_t( struct _PDCLIB_file_t * stream ); + +/* Sanity checking and preparing of read buffer, should be called first thing + by any stdio read-data function. + Returns 0 on success, EOF on error. + On error, EOF / error flags and errno are set appropriately. +*/ +_PDCLIB_LOCAL int _PDCLIB_prepread( struct _PDCLIB_file_t * stream ); + +/* Sanity checking, should be called first thing by any stdio write-data + function. + Returns 0 on success, EOF on error. + On error, error flags and errno are set appropriately. +*/ +_PDCLIB_LOCAL int _PDCLIB_prepwrite( struct _PDCLIB_file_t * stream ); + +/* Closing all streams on program exit */ +_PDCLIB_LOCAL void _PDCLIB_closeall( void ); + +/* Check if a given year is a leap year. Parameter is offset to 1900. */ +_PDCLIB_LOCAL int _PDCLIB_is_leap( int year_offset ); + +/* Read a specified number of lines from a file stream; return a pointer to + allocated memory holding the lines (newlines replaced with zero terminators) + or NULL in case of error. +*/ +_PDCLIB_LOCAL char * _PDCLIB_load_lines( struct _PDCLIB_file_t * stream, _PDCLIB_size_t lines ); + +/* Returns the (locale dependent) error message associated with the argument + errno value. +*/ +_PDCLIB_LOCAL char * _PDCLIB_geterrtext( int errnum ); + +/* Returns non-zero if the given stream is on the internal list of open files, + zero otherwise. Sets the second paramenter (if not NULL) to the previous + stream on the list (or NULL if the given stream is the first on the list). + This function does not lock _PDCLIB_filelist_mtx, this needs to be done by + the calling function (_PDCLIB_getstream() or freopen()). +*/ +_PDCLIB_LOCAL int _PDCLIB_isstream( struct _PDCLIB_file_t * stream, struct _PDCLIB_file_t ** previous ); + +/* Removes the given stream from the internal list of open files. Returns zero + if successful, non-zero otherwise. In case of error, sets errno to EBADF. + This function does not lock _PDCLIB_filelist_mtx, this needs to be done by + the calling function (fclose()). +*/ +_PDCLIB_LOCAL int _PDCLIB_getstream( struct _PDCLIB_file_t * stream ); + +/* Backend for strtok and strtok_s (plus potential extensions like strtok_r). */ +_PDCLIB_LOCAL char * _PDCLIB_strtok( char * _PDCLIB_restrict s1, _PDCLIB_size_t * _PDCLIB_restrict s1max, const char * _PDCLIB_restrict s2, char ** _PDCLIB_restrict ptr ); + +/* -------------------------------------------------------------------------- */ +/* errno */ +/* -------------------------------------------------------------------------- */ + +/* A mechanism for delayed evaluation. + If PDCLib would call its error number "errno" directly, there would be no way + to catch its value from underlying system calls that also use it (i.e., POSIX + operating systems). That is why we use an internal name, providing a means to + access it through . +*/ +_PDCLIB_PUBLIC int * _PDCLIB_errno_func( void ); + +/* -------------------------------------------------------------------------- */ +/* support */ +/* -------------------------------------------------------------------------- */ + +#define _PDCLIB_LC_ALL 0 +#define _PDCLIB_LC_COLLATE 1 +#define _PDCLIB_LC_CTYPE 2 +#define _PDCLIB_LC_MONETARY 3 +#define _PDCLIB_LC_NUMERIC 4 +#define _PDCLIB_LC_TIME 5 +#define _PDCLIB_LC_MESSAGES 6 +#define _PDCLIB_LC_COUNT 7 + +#define _PDCLIB_CTYPE_ALPHA 1 +#define _PDCLIB_CTYPE_BLANK 2 +#define _PDCLIB_CTYPE_CNTRL 4 +#define _PDCLIB_CTYPE_GRAPH 8 +#define _PDCLIB_CTYPE_PUNCT 16 +#define _PDCLIB_CTYPE_SPACE 32 +#define _PDCLIB_CTYPE_LOWER 64 +#define _PDCLIB_CTYPE_UPPER 128 + +#define _PDCLIB_CHARSET_SIZE ( 1 << _PDCLIB_CHAR_BIT ) + +struct _PDCLIB_lc_lconv_numeric_t +{ + char * decimal_point; + char * thousands_sep; + char * grouping; +}; + +struct _PDCLIB_lc_lconv_monetary_t +{ + char * mon_decimal_point; + char * mon_thousands_sep; + char * mon_grouping; + char * positive_sign; + char * negative_sign; + char * currency_symbol; + char * int_curr_symbol; + char frac_digits; + char p_cs_precedes; + char n_cs_precedes; + char p_sep_by_space; + char n_sep_by_space; + char p_sign_posn; + char n_sign_posn; + char int_frac_digits; + char int_p_cs_precedes; + char int_n_cs_precedes; + char int_p_sep_by_space; + char int_n_sep_by_space; + char int_p_sign_posn; + char int_n_sign_posn; +}; + +struct _PDCLIB_lc_numeric_monetary_t +{ + struct lconv * lconv; + int numeric_alloced; + int monetary_alloced; +}; + +extern struct _PDCLIB_lc_numeric_monetary_t _PDCLIB_lc_numeric_monetary; + +struct _PDCLIB_lc_collate_t +{ + int alloced; + /* 1..3 code points */ + /* 1..8, 18 collation elements of 3 16-bit integers */ +}; + +extern struct _PDCLIB_lc_collate_t _PDCLIB_lc_collate_C; +extern struct _PDCLIB_lc_collate_t * _PDCLIB_lc_collate; + +/* One entry to the _PDCLIB_lc_ctype_t.entry data table */ +struct _PDCLIB_lc_ctype_entry_t +{ + _PDCLIB_uint_least16_t flags; /* Whether a character is of a given CTYPE */ + unsigned char upper; /* Result for toupper() */ + unsigned char lower; /* Result for tolower() */ +}; + +struct _PDCLIB_lc_ctype_t +{ + int alloced; /* .entry dynamically allocated? */ + int digits_low; /* Where decimal digits start */ + int digits_high; /* Where decimal digits end */ + int Xdigits_low; /* Where A..F start */ + int Xdigits_high; /* Where A..F end */ + int xdigits_low; /* Where a..f start */ + int xdigits_high; /* Where a..f end */ + struct _PDCLIB_lc_ctype_entry_t * entry; /* The data table */ +}; + +extern struct _PDCLIB_lc_ctype_t _PDCLIB_lc_ctype_C; +extern struct _PDCLIB_lc_ctype_t * _PDCLIB_lc_ctype; + +struct _PDCLIB_lc_messages_t +{ + int alloced; + char * errno_texts[_PDCLIB_ERRNO_MAX]; /* strerror() / perror() */ +}; + +extern struct _PDCLIB_lc_messages_t _PDCLIB_lc_messages_C; +extern struct _PDCLIB_lc_messages_t * _PDCLIB_lc_messages; + +struct _PDCLIB_lc_time_t +{ + int alloced; + char * month_name_abbr[12]; /* month names, abbreviated */ + char * month_name_full[12]; /* month names, full */ + char * day_name_abbr[7]; /* weekday names, abbreviated */ + char * day_name_full[7]; /* weekday names, full */ + char * date_time_format; /* date / time format for strftime( "%c" ) */ + char * time_format_12h; /* 12-hour time format for strftime( "%r" ) */ + char * date_format; /* date format for strftime( "%x" ) */ + char * time_format; /* time format for strftime( "%X" ) */ + char * am_pm[2]; /* AM / PM designation */ +}; + +extern struct _PDCLIB_lc_time_t _PDCLIB_lc_time_C; +extern struct _PDCLIB_lc_time_t * _PDCLIB_lc_time; + +_PDCLIB_LOCAL struct _PDCLIB_lc_lconv_numeric_t * _PDCLIB_load_lc_numeric( const char * path, const char * locale ); +_PDCLIB_LOCAL struct _PDCLIB_lc_lconv_monetary_t * _PDCLIB_load_lc_monetary( const char * path, const char * locale ); +_PDCLIB_LOCAL struct _PDCLIB_lc_collate_t * _PDCLIB_load_lc_collate( const char * path, const char * locale ); +_PDCLIB_LOCAL struct _PDCLIB_lc_ctype_t * _PDCLIB_load_lc_ctype( const char * path, const char * locale ); +_PDCLIB_LOCAL struct _PDCLIB_lc_time_t * _PDCLIB_load_lc_time( const char * path, const char * locale ); +_PDCLIB_LOCAL struct _PDCLIB_lc_messages_t * _PDCLIB_load_lc_messages( const char * path, const char * locale ); + +/* -------------------------------------------------------------------------- */ +/* _PDCLIB_bigint_t support (required for floating point conversions) */ +/* -------------------------------------------------------------------------- */ + +/* Must be divisible by 32. */ +#define _PDCLIB_BIGINT_BITS 1024 + +#if _PDCLIB_BIGINT_DIGIT_BITS == 32 +#define _PDCLIB_BIGINT_DIGIT_MAX UINT32_C( 0xFFFFFFFF ) +#define _PDCLIB_BIGINT_BASE ( UINT64_C(1) << _PDCLIB_BIGINT_DIGIT_BITS ) +typedef _PDCLIB_uint_least32_t _PDCLIB_bigint_digit_t; +typedef _PDCLIB_uint_least64_t _PDCLIB_bigint_arith_t; +typedef _PDCLIB_int_least64_t _PDCLIB_bigint_sarith_t; +#elif _PDCLIB_BIGINT_DIGIT_BITS == 16 +#define _PDCLIB_BIGINT_DIGIT_MAX UINT16_C( 0xFFFF ) +#define _PDCLIB_BIGINT_BASE ( UINT32_C(1) << _PDCLIB_BIGINT_DIGIT_BITS ) +typedef _PDCLIB_uint_least16_t _PDCLIB_bigint_digit_t; +typedef _PDCLIB_uint_least32_t _PDCLIB_bigint_arith_t; +typedef _PDCLIB_int_least32_t _PDCLIB_bigint_sarith_t; +#elif _PDCLIB_BIGINT_DIGIT_BITS == 8 +/* For testing purposes only. */ +#define _PDCLIB_BIGINT_DIGIT_MAX UINT8_C( 0xFF ) +#define _PDCLIB_BIGINT_BASE ( UINT16_C(1) << _PDCLIB_BIGINT_DIGIT_BITS ) +typedef _PDCLIB_uint_least8_t _PDCLIB_bigint_digit_t; +typedef _PDCLIB_uint_least16_t _PDCLIB_bigint_arith_t; +typedef _PDCLIB_int_least16_t _PDCLIB_bigint_sarith_t; +#else +#error Only 16 or 32 supported for _PDCLIB_BIGINT_DIGIT_BITS. +#endif + +/* How many "digits" a _PDCLIB_bigint_t holds. */ +#define _PDCLIB_BIGINT_DIGITS _PDCLIB_BIGINT_BITS / _PDCLIB_BIGINT_DIGIT_BITS + +/* Maximum number of characters needed for _PDCLIB_bigint_tostring() */ +#define _PDCLIB_BIGINT_CHARS ( _PDCLIB_BIGINT_BITS / 4 + _PDCLIB_BIGINT_DIGITS + 2 ) + +/* Type */ +/* ---- */ + +typedef struct +{ + /* Least significant digit first */ + _PDCLIB_bigint_digit_t data[ _PDCLIB_BIGINT_DIGITS ]; + /* Number of digits used; zero value == zero size */ + _PDCLIB_size_t size; +} _PDCLIB_bigint_t; + +/* Initializer */ +/* ----------- */ + +/* Sets a bigint to pow2( n ) */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint2( _PDCLIB_bigint_t * bigint, unsigned n ); + +/* Sets a bigint to pow10( n ) */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint10( _PDCLIB_bigint_t * bigint, unsigned n ); + +/* Sets a bigint from a 32bit input value. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint32( _PDCLIB_bigint_t * bigint, _PDCLIB_uint_least32_t value ); + +/* Sets a bigint from two 32bit input values. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint64( _PDCLIB_bigint_t * bigint, _PDCLIB_uint_least32_t high, _PDCLIB_uint_least32_t low ); + +/* Sets a bigint from another bigint. (Copies only value->size digits, so it is + faster than a POD copy of a _PDCLIB_bigint_t in most cases.) +*/ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint( _PDCLIB_bigint_t * _PDCLIB_restrict bigint, _PDCLIB_bigint_t const * _PDCLIB_restrict value ); + +/* Comparison, Output */ +/* ------------------ */ + +/* Compares two given bigint values. Returns 0 if lhs == rhs, a negative number + if lhs < rhs, and a positive number if lhs > rhs. +*/ +_PDCLIB_LOCAL int _PDCLIB_bigint_cmp( _PDCLIB_bigint_t const * _PDCLIB_restrict lhs, _PDCLIB_bigint_t const * _PDCLIB_restrict rhs ); + +/* Writes a hexadecimal representation of the given bigint into the given buffer. + Buffer should be at least _PDCLIB_BIGINT_CHARS in size. +*/ +_PDCLIB_LOCAL char * _PDCLIB_bigint_tostring( _PDCLIB_bigint_t const * _PDCLIB_restrict value, char * _PDCLIB_restrict buffer ); + +/* Operations (in-place) */ +/* --------------------- */ + +/* Adds to a given bigint another given bigint. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_add( _PDCLIB_bigint_t * _PDCLIB_restrict lhs, _PDCLIB_bigint_t const * _PDCLIB_restrict rhs ); + +/* Substracts from a given bigint another given bigint. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_sub( _PDCLIB_bigint_t * _PDCLIB_restrict lhs, _PDCLIB_bigint_t const * _PDCLIB_restrict rhs ); + +/* Multiplies a given bigint with a given 32bit value. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_mul_dig( _PDCLIB_bigint_t * lhs, _PDCLIB_bigint_digit_t rhs ); + +/* Divides a given bigint by a given 32bit value. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_div_dig( _PDCLIB_bigint_t * lhs, _PDCLIB_bigint_digit_t rhs ); + +/* Shifts a given bigint left by a given count of bits. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_shl( _PDCLIB_bigint_t * lhs, unsigned rhs ); + +/* Operations (into new bigint) */ +/* ---------------------------- */ + +/* Multiplies a given bigint with another given bigint. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_mul( _PDCLIB_bigint_t * _PDCLIB_restrict result, _PDCLIB_bigint_t const * _PDCLIB_restrict lhs, _PDCLIB_bigint_t const * _PDCLIB_restrict rhs ); + +/* Divides a given bigint by another given bigint. */ +_PDCLIB_LOCAL _PDCLIB_bigint_t * _PDCLIB_bigint_div( _PDCLIB_bigint_t * _PDCLIB_restrict result, _PDCLIB_bigint_t const * _PDCLIB_restrict lhs, _PDCLIB_bigint_t const * _PDCLIB_restrict rhs ); + +/* Queries */ +/* ------- */ + +/* Returns the log2() of a given bigint */ +_PDCLIB_LOCAL unsigned _PDCLIB_bigint_log2( _PDCLIB_bigint_t const * bigint ); + +/* FP Conversions */ +/* -------------- */ + +/* Split a float into its integral components. + Returns 1 if value is negative, zero otherwise. +*/ +_PDCLIB_LOCAL int _PDCLIB_float_split( float value, unsigned * exponent, _PDCLIB_bigint_t * significand ); + +/* Split a double into its integral components. + Returns 1 if value is negative, zero otherwise. +*/ +_PDCLIB_LOCAL int _PDCLIB_double_split( double value, unsigned * exponent, _PDCLIB_bigint_t * significand ); + +/* Split a long double into its integral components. + Returns 1 if value is negative, zero otherwise. +*/ +_PDCLIB_LOCAL int _PDCLIB_long_double_split( long double value, unsigned * exponent, _PDCLIB_bigint_t * significand ); + +/* -------------------------------------------------------------------------- */ +/* Sanity checks */ +/* -------------------------------------------------------------------------- */ + +/* signed-ness of char */ +_PDCLIB_static_assert( _PDCLIB_CHAR_MIN == ((((char) -1) < 0) ? _PDCLIB_SCHAR_MIN : 0), "Compiler disagrees on signed-ness of 'char'." ); + +/* two's complement */ +#if _PDCLIB_TWOS_COMPLEMENT == 1 +#if _PDCLIB_CHAR_MIN < 0 +_PDCLIB_static_assert( ((char) ~ (char) 0 < 0), "Not two's complement on 'char'." ); +#endif +_PDCLIB_static_assert( ((short) ~ (short) 0 < 0), "Not two's complement on 'short'." ); +_PDCLIB_static_assert( ((int) ~ (int) 0 < 0), "Not two's complement on 'int'." ); +_PDCLIB_static_assert( ((long) ~ (long) 0 < 0), "Not two's complement on 'long'." ); +_PDCLIB_static_assert( ((long long) ~ (long long) 0 < 0), "Not two's complement on 'long long'." ); +#endif + +/* size_t as the result of sizeof */ +_PDCLIB_static_assert( sizeof( sizeof( int ) ) == sizeof( _PDCLIB_size_t ), "Compiler disagrees on size_t." ); + +/* wchar_t as the type of wide character literals */ +_PDCLIB_static_assert( sizeof( _PDCLIB_wchar_t ) == sizeof( L'x' ), "Compiler disagrees on wchar_t." ); +#ifdef __cplusplus +_PDCLIB_static_assert( sizeof( _PDCLIB_wchar_t ) == sizeof( wchar_t ), "Compiler disagrees on wchar_t (C++)." ); +#endif + +/* intptr_t/uintptr_t being wide enough to store the value of a pointer */ +_PDCLIB_static_assert( sizeof( void * ) == sizeof( _PDCLIB_intptr_t ), "Compiler disagrees on intptr_t." ); +_PDCLIB_static_assert( sizeof( void * ) == sizeof( _PDCLIB_uintptr_t ), "Compiler disagrees on uintptr_t." ); + +/* ptrdiff_t as the result of pointer arithmetic */ +_PDCLIB_static_assert( sizeof( &_PDCLIB_digits[1] - &_PDCLIB_digits[0] ) == sizeof( _PDCLIB_ptrdiff_t ), "Compiler disagrees on ptrdiff_t." ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/pdclib/_PDCLIB_lib_ext1.h b/libc/include/pdclib/_PDCLIB_lib_ext1.h new file mode 100644 index 0000000..fe96bca --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_lib_ext1.h @@ -0,0 +1,32 @@ +/* __STDC_WANT_LIB_EXT1__ redefinition guard + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef __STDC_WANT_LIB_EXT1__ + #ifdef __STDC_WANT_LIB_EXT1_PREVIOUS__ + #if __STDC_WANT_LIB_EXT1_PREVIOUS__ != -1 + #error __STDC_WANT_LIB_EXT1__ undefined when it was defined earlier. + #endif + #else + #define __STDC_WANT_LIB_EXT1_PREVIOUS__ -1 + #endif +#else + #if ( __STDC_WANT_LIB_EXT1__ + 0 ) == 0 && ( 0 - __STDC_WANT_LIB_EXT1__ - 1 ) == 1 + #error __STDC_WANT_LIB_EXT1__ defined but empty. Should be an integer value. + #endif + #ifdef __STDC_WANT_LIB_EXT1_PREVIOUS__ + #if ( __STDC_WANT_LIB_EXT1__ + 0 ) != __STDC_WANT_LIB_EXT1_PREVIOUS__ + #error __STDC_WANT_LIB_EXT1__ redefined from previous value. + #endif + #else + #if ( __STDC_WANT_LIB_EXT1__ + 0 ) == 0 + #define __STDC_WANT_LIB_EXT1_PREVIOUS__ 0 + #elif ( __STDC_WANT_LIB_EXT1__ + 0 ) == 1 + #define __STDC_WANT_LIB_EXT1_PREVIOUS__ 1 + #else + #error __STDC_WANT_LIB_EXT1__ set to value other than 0,1 -- undefined behavior + #endif + #endif +#endif diff --git a/libc/include/pdclib/_PDCLIB_print.h b/libc/include/pdclib/_PDCLIB_print.h new file mode 100644 index 0000000..da98c1a --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_print.h @@ -0,0 +1,71 @@ +/* PDCLib printf logic <_PDCLIB_print.h> + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_PRINT_H +#define _PDCLIB_PRINT_H _PDCLIB_PRINT_H + +#include "pdclib/_PDCLIB_internal.h" + +#include +#include +#include + +/* This macro delivers a given character to either a memory buffer or a stream, + depending on the contents of 'status' (struct _PDCLIB_status_t). + x - the character to be delivered + i - pointer to number of characters already delivered in this call + n - pointer to maximum number of characters to be delivered in this call + s - the buffer into which the character shall be delivered +*/ +#define PUT( x ) \ + do { \ + int character = x; \ + if ( status->i < status->n ) { \ + if ( status->stream != NULL ) \ + putc( character, status->stream ); \ + else \ + status->s[status->i] = character; \ + } \ + ++(status->i); \ + } while ( 0 ) + + +/* Using an integer's bits as flags for both the conversion flags and length + modifiers. +*/ +#define E_minus (INT32_C(1)<<0) +#define E_plus (INT32_C(1)<<1) +#define E_alt (INT32_C(1)<<2) +#define E_space (INT32_C(1)<<3) +#define E_zero (INT32_C(1)<<4) +#define E_done (INT32_C(1)<<5) + +#define E_char (INT32_C(1)<<6) +#define E_short (INT32_C(1)<<7) +#define E_long (INT32_C(1)<<8) +#define E_llong (INT32_C(1)<<9) +#define E_intmax (INT32_C(1)<<10) +#define E_size (INT32_C(1)<<11) +#define E_ptrdiff (INT32_C(1)<<12) +#define E_pointer (INT32_C(1)<<13) + +#define E_double (INT32_C(1)<<14) +#define E_ldouble (INT32_C(1)<<15) + +#define E_decimal (INT32_C(1)<<18) +#define E_exponent (INT32_C(1)<<19) +#define E_generic (INT32_C(1)<<20) +#define E_hexa (INT32_C(1)<<21) + +#define E_lower (INT32_C(1)<<16) +#define E_unsigned (INT32_C(1)<<17) + +void _PDCLIB_print_integer( struct _PDCLIB_imaxdiv_t div, struct _PDCLIB_status_t * status ); +void _PDCLIB_print_string( const char * s, struct _PDCLIB_status_t * status ); +void _PDCLIB_print_double( double value, struct _PDCLIB_status_t * status ); +void _PDCLIB_print_ldouble( long double value, struct _PDCLIB_status_t * status ); + +#endif diff --git a/libc/include/pdclib/_PDCLIB_tzcode.h b/libc/include/pdclib/_PDCLIB_tzcode.h new file mode 100644 index 0000000..d12c4a7 --- /dev/null +++ b/libc/include/pdclib/_PDCLIB_tzcode.h @@ -0,0 +1,153 @@ +/* TZ Code declarations and definitions <_PDCLIB_tzcode.h> + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_TZCODE_H +#define _PDCLIB_TZCODE_H _PDCLIB_TZCODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include +#include + +/* Handy macros that are independent of tzfile implementation. */ +#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */ + +#define SECSPERMIN 60 +#define MINSPERHOUR 60 +#define HOURSPERDAY 24 +#define DAYSPERWEEK 7 +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) +#define MONSPERYEAR 12 + +#define AVGSECSPERYEAR 31556952L +#define SECSPERREPEAT ((int_fast64_t) YEARSPERREPEAT * (int_fast64_t) AVGSECSPERYEAR) +#define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */ + +#define TM_SUNDAY 0 +#define TM_MONDAY 1 +#define TM_TUESDAY 2 +#define TM_WEDNESDAY 3 +#define TM_THURSDAY 4 +#define TM_FRIDAY 5 +#define TM_SATURDAY 6 + +#define TM_YEAR_BASE 1900 + +#define EPOCH_YEAR 1970 +#define EPOCH_WDAY TM_THURSDAY + +extern struct tm _PDCLIB_tm; +extern int lcl_is_set; + +static const char gmt[] = "GMT"; + +static const int mon_lengths[ 2 ][ MONSPERYEAR ] = +{ + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +static const int year_lengths[2] = +{ + DAYSPERNYEAR, DAYSPERLYEAR +}; + +/* time type information */ +struct ttinfo +{ + int_fast32_t utoff; /* UT offset in seconds */ + bool isdst; /* used to set tm_isdst */ + int desigidx; /* abbreviation list index */ + bool ttisstd; /* transition is std time */ + bool ttisut; /* transition is UT */ +}; + +/* leap second information */ +struct lsinfo +{ + time_t trans; /* transition time */ + int_fast64_t corr; /* correction to apply */ +}; + +#define BIGGEST( a, b ) (((a) > (b)) ? (a) : (b)) + +#ifndef TZ_MAX_TIMES +#define TZ_MAX_TIMES 2000 +#endif + +#ifndef TZ_MAX_TYPES +/* This must be at least 17 for Europe/Vilnius. */ +/* Limited by what (unsigned char)s can hold */ +#define TZ_MAX_TYPES 256 +#endif + +#ifndef TZ_MAX_CHARS +/* Maximum number of abbreviation characters */ +/* Limited by what (unsigned char)s can hold */ +#define TZ_MAX_CHARS 50 +#endif + +#ifndef TZ_MAX_LEAPS +/* Maximum number of leap second corrections */ +#define TZ_MAX_LEAPS 50 +#endif + +#ifdef TZNAME_MAX +#define MY_TZNAME_MAX TZNAME_MAX +#else +#define MY_TZNAME_MAX 255 +#endif + +struct state +{ + int leapcnt; + int timecnt; + int typecnt; + int charcnt; + bool goback; + bool goahead; + time_t ats[ TZ_MAX_TIMES ]; + unsigned char types[ TZ_MAX_TIMES ]; + struct ttinfo ttis[ TZ_MAX_TYPES ]; + char chars[ BIGGEST( BIGGEST( TZ_MAX_CHARS + 1, sizeof gmt ), ( 2 * ( MY_TZNAME_MAX + 1 ) ) ) ]; + struct lsinfo lsis[ TZ_MAX_LEAPS ]; + + /* The time type to use for early times or if no transitions. + It is always zero for recent tzdb releases. + It might be nonzero for data from tzdb 2018e or earlier. + */ + int defaulttype; +}; + +extern struct state _PDCLIB_lclmem; +extern struct state _PDCLIB_gmtmem; + +void _PDCLIB_gmtcheck(void); +struct tm * _PDCLIB_gmtsub( struct state const * sp, time_t const * timep, int_fast32_t offset, struct tm * tmp ); +bool _PDCLIB_increment_overflow( int * ip, int j ); +void _PDCLIB_init_ttinfo( struct ttinfo * s, int_fast32_t utoff, bool isdst, int desigidx ); +struct tm * _PDCLIB_localsub( struct state const * sp, time_t const * timep, int_fast32_t setname, struct tm * const tmp ); +struct tm * _PDCLIB_localtime_tzset( time_t const * timep, struct tm * tmp, bool setname ); +time_t _PDCLIB_mktime_tzname( struct state * sp, struct tm * tmp, bool setname ); +struct tm * _PDCLIB_timesub( const time_t * timep, int_fast32_t offset, const struct state * sp, struct tm * tmp ); +int _PDCLIB_tzload( char const * name, struct state * sp, bool doextend ); +bool _PDCLIB_tzparse(char const *, struct state *, bool); +void _PDCLIB_tzset_unlocked( void ); +void _PDCLIB_update_tzname_etc( struct state const * sp, struct ttinfo const * ttisp ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/signal.h b/libc/include/signal.h new file mode 100644 index 0000000..1f679ec --- /dev/null +++ b/libc/include/signal.h @@ -0,0 +1,91 @@ +/* Signal handling + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_SIGNAL_H +#define _PDCLIB_SIGNAL_H _PDCLIB_SIGNAL_H + +#include "pdclib/_PDCLIB_internal.h" + +/* Signals ------------------------------------------------------------------ */ + +/* A word on signals, to the people using PDCLib in their OS projects. + + The definitions of the C standard leave about everything that *could* be + useful to be "implementation defined". Without additional, non-standard + arrangements, it is not possible to turn them into a useful tool. + + This example implementation chose to "not generate any of these signals, + except as a result of explicit calls to the raise function", which is + allowed by the standard but of course does nothing for the usefulness of + . + + A useful signal handling would: + 1) make signal() a system call that registers the signal handler with the OS + 2) make raise() a system call triggering an OS signal to the running process + 3) make provisions that further signals of the same type are blocked until + the signal handler returns (optional for SIGILL) +*/ + +/* These are the values used by Linux. */ + +/* Abnormal termination / abort() */ +#define SIGABRT 6 +/* Arithmetic exception / division by zero / overflow */ +#define SIGFPE 8 +/* Illegal instruction */ +#define SIGILL 4 +/* Interactive attention signal */ +#define SIGINT 2 +/* Invalid memory access */ +#define SIGSEGV 11 +/* Termination request */ +#define SIGTERM 15 + +/* The following should be defined to pointer values that could NEVER point to + a valid signal handler function. (They are used as special arguments to + signal().) Again, these are the values used by Linux. +*/ +#define SIG_DFL (void (*)( int ))0 +#define SIG_ERR (void (*)( int ))-1 +#define SIG_IGN (void (*)( int ))1 + +typedef _PDCLIB_sig_atomic_t sig_atomic_t; + +/* Installs a signal handler "func" for the given signal. + A signal handler is a function that takes an integer as argument (the signal + number) and returns void. + + Note that a signal handler can do very little else than: + 1) assign a value to a static object of type "volatile sig_atomic_t", + 2) call signal() with the value of sig equal to the signal received, + 3) call _Exit(), + 4) call abort(). + Virtually everything else is undefind. + + The signal() function returns the previous installed signal handler, which + at program start may be SIG_DFL or SIG_ILL. (This implementation uses + SIG_DFL for all handlers.) If the request cannot be honored, SIG_ERR is + returned and errno is set to an unspecified positive value. +*/ +_PDCLIB_PUBLIC void ( *signal( int sig, void ( *func )( int ) ) )( int ); + +/* Raises the given signal (executing the registered signal handler with the + given signal number as parameter). + This implementation does not prevent further signals of the same time from + occuring, but executes signal( sig, SIG_DFL ) before entering the signal + handler (i.e., a second signal before the signal handler re-registers itself + or SIG_IGN will end the program). + Returns zero if successful, nonzero otherwise. */ +_PDCLIB_PUBLIC int raise( int sig ); + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_SIGNAL_H +#include _PDCLIB_EXTEND_SIGNAL_H +#endif + +#endif diff --git a/libc/include/stdalign.h b/libc/include/stdalign.h new file mode 100644 index 0000000..685b052 --- /dev/null +++ b/libc/include/stdalign.h @@ -0,0 +1,26 @@ +/* Alignment + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDALIGN_H +#define _PDCLIB_ALIGN_H _PDCLIB_ALIGN_H + +#ifndef __cplusplus +#define alignas _Alignas +#define alignof _Alignof +#endif + +#define __alignas_is_defined 1 +#define __alignof_is_defined 1 + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDALIGN_H +#include _PDCLIB_EXTEND_STDALIGN_H +#endif + +#endif + diff --git a/libc/include/stdarg.h b/libc/include/stdarg.h new file mode 100644 index 0000000..7741b70 --- /dev/null +++ b/libc/include/stdarg.h @@ -0,0 +1,34 @@ +/* Variable arguments + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDARG_H +#define _PDCLIB_STDARG_H _PDCLIB_STDARG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_config.h" + +typedef _PDCLIB_va_list va_list; + +#define va_arg( ap, type ) _PDCLIB_va_arg( ap, type ) +#define va_copy( dest, src ) _PDCLIB_va_copy( dest, src ) +#define va_end( ap ) _PDCLIB_va_end( ap ) +#define va_start( ap, parmN ) _PDCLIB_va_start( ap, parmN ) + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDARG_H +#include _PDCLIB_EXTEND_STDARG_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/stdbool.h b/libc/include/stdbool.h new file mode 100644 index 0000000..e901f63 --- /dev/null +++ b/libc/include/stdbool.h @@ -0,0 +1,24 @@ +/* Boolean type and values + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDBOOL_H +#define _PDCLIB_STDBOOL_H _PDCLIB_STDBOOL_H + +#ifndef __cplusplus +#define bool _Bool +#define true 1 +#define false 0 +#endif +#define __bool_true_false_are_defined 1 + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDBOOL_H +#include _PDCLIB_EXTEND_STDBOOL_H +#endif + +#endif diff --git a/libc/include/stddef.h b/libc/include/stddef.h new file mode 100644 index 0000000..7d03d07 --- /dev/null +++ b/libc/include/stddef.h @@ -0,0 +1,55 @@ +/* Common definitions + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDDEF_H +#define _PDCLIB_STDDEF_H _PDCLIB_STDDEF_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +typedef _PDCLIB_ptrdiff_t ptrdiff_t; + +#ifndef _PDCLIB_SIZE_T_DEFINED +#define _PDCLIB_SIZE_T_DEFINED _PDCLIB_SIZE_T_DEFINED +typedef _PDCLIB_size_t size_t; +#endif + +#ifndef __cplusplus +typedef _PDCLIB_wchar_t wchar_t; +#endif + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +#define offsetof( type, member ) _PDCLIB_offsetof( type, member ) + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 +#ifndef _PDCLIB_RSIZE_T_DEFINED +#define _PDCLIB_RSIZE_T_DEFINED _PDCLIB_RSIZE_T_DEFINED +typedef size_t rsize_t; +#endif +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDDEF_H +#include _PDCLIB_EXTEND_STDDEF_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/stdint.h b/libc/include/stdint.h new file mode 100644 index 0000000..04af6b0 --- /dev/null +++ b/libc/include/stdint.h @@ -0,0 +1,236 @@ +/* Integer types + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDINT_H +#define _PDCLIB_STDINT_H _PDCLIB_STDINT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +/* 7.18.1.1 Exact-width integer types. */ + +#ifdef _PDCLIB_int8_t +typedef _PDCLIB_int8_t int8_t; +typedef _PDCLIB_uint8_t uint8_t; +#endif + +#ifdef _PDCLIB_int16_t +typedef _PDCLIB_int16_t int16_t; +typedef _PDCLIB_uint16_t uint16_t; +#endif + +#ifdef _PDCLIB_int32_t +typedef _PDCLIB_int32_t int32_t; +typedef _PDCLIB_uint32_t uint32_t; +#endif + +#ifdef _PDCLIB_int64_t +typedef _PDCLIB_int64_t int64_t; +typedef _PDCLIB_uint64_t uint64_t; +#endif + +/* 7.18.1.2 Minimum-width integer types */ + +/* You are allowed to add more types here, e.g. int_least24_t. */ + +typedef _PDCLIB_int_least8_t int_least8_t; +typedef _PDCLIB_int_least16_t int_least16_t; +typedef _PDCLIB_int_least32_t int_least32_t; +typedef _PDCLIB_int_least64_t int_least64_t; + +typedef _PDCLIB_uint_least8_t uint_least8_t; +typedef _PDCLIB_uint_least16_t uint_least16_t; +typedef _PDCLIB_uint_least32_t uint_least32_t; +typedef _PDCLIB_uint_least64_t uint_least64_t; + +/* 7.18.1.3 Fastest minimum-width integer types */ + +/* You are allowed to add more types here, e.g. int_fast24_t. */ + +typedef _PDCLIB_int_fast8_t int_fast8_t; +typedef _PDCLIB_int_fast16_t int_fast16_t; +typedef _PDCLIB_int_fast32_t int_fast32_t; +typedef _PDCLIB_int_fast64_t int_fast64_t; + +typedef _PDCLIB_uint_fast8_t uint_fast8_t; +typedef _PDCLIB_uint_fast16_t uint_fast16_t; +typedef _PDCLIB_uint_fast32_t uint_fast32_t; +typedef _PDCLIB_uint_fast64_t uint_fast64_t; + +/* 7.18.1.4 Integer types capable of holding object pointers */ + +typedef _PDCLIB_intptr_t intptr_t; +typedef _PDCLIB_uintptr_t uintptr_t; + +/* 7.18.1.5 Greatest-width integer types */ + +typedef _PDCLIB_intmax_t intmax_t; +typedef _PDCLIB_uintmax_t uintmax_t; + +/* 7.18.2 Limits of specified-width integer types */ + +#if defined( __cplusplus ) && __cplusplus < 201103L +#ifndef __STDC_LIMIT_MACROS +#define _PDCLIB_NO_LIMIT_MACROS +#endif +#endif + +#ifndef _PDCLIB_NO_LIMIT_MACROS + +/* 7.18.2.1 Limits of exact-width integer types */ + +#if _PDCLIB_TWOS_COMPLEMENT == 1 + +#if _PDCLIB_INT_LEAST8_MAX == 0x7f +#define INT8_MAX _PDCLIB_INT_LEAST8_MAX +#define INT8_MIN _PDCLIB_INT_LEAST8_MIN +#define UINT8_MAX _PDCLIB_UINT_LEAST8_MAX +#endif + +#if _PDCLIB_INT_LEAST16_MAX == 0x7fff +#define INT16_MAX _PDCLIB_INT_LEAST16_MAX +#define INT16_MIN _PDCLIB_INT_LEAST16_MIN +#define UINT16_MAX _PDCLIB_UINT_LEAST16_MAX +#endif + +#if _PDCLIB_INT_LEAST32_MAX == 0x7fffffffl +#define INT32_MAX _PDCLIB_INT_LEAST32_MAX +#define INT32_MIN _PDCLIB_INT_LEAST32_MIN +#define UINT32_MAX _PDCLIB_UINT_LEAST32_MAX +#endif + +#if _PDCLIB_INT_LEAST64_MAX == 0x7fffffffffffffffll +#define INT64_MAX _PDCLIB_INT_LEAST64_MAX +#define INT64_MIN _PDCLIB_INT_LEAST64_MIN +#define UINT64_MAX _PDCLIB_UINT_LEAST64_MAX +#endif + +#endif + +/* 7.18.2.2 Limits of minimum-width integer types */ + +#define INT_LEAST8_MIN _PDCLIB_INT_LEAST8_MIN +#define INT_LEAST8_MAX _PDCLIB_INT_LEAST8_MAX +#define UINT_LEAST8_MAX _PDCLIB_UINT_LEAST8_MAX + +#define INT_LEAST16_MIN _PDCLIB_INT_LEAST16_MIN +#define INT_LEAST16_MAX _PDCLIB_INT_LEAST16_MAX +#define UINT_LEAST16_MAX _PDCLIB_UINT_LEAST16_MAX + +#define INT_LEAST32_MIN _PDCLIB_INT_LEAST32_MIN +#define INT_LEAST32_MAX _PDCLIB_INT_LEAST32_MAX +#define UINT_LEAST32_MAX _PDCLIB_UINT_LEAST32_MAX + +#define INT_LEAST64_MIN _PDCLIB_INT_LEAST64_MIN +#define INT_LEAST64_MAX _PDCLIB_INT_LEAST64_MAX +#define UINT_LEAST64_MAX _PDCLIB_UINT_LEAST64_MAX + +/* 7.18.2.3 Limits of fastest minimum-width integer types */ + +#define INT_FAST8_MIN _PDCLIB_INT_FAST8_MIN +#define INT_FAST8_MAX _PDCLIB_INT_FAST8_MAX +#define UINT_FAST8_MAX _PDCLIB_UINT_FAST8_MAX + +#define INT_FAST16_MIN _PDCLIB_INT_FAST16_MIN +#define INT_FAST16_MAX _PDCLIB_INT_FAST16_MAX +#define UINT_FAST16_MAX _PDCLIB_UINT_FAST16_MAX + +#define INT_FAST32_MIN _PDCLIB_INT_FAST32_MIN +#define INT_FAST32_MAX _PDCLIB_INT_FAST32_MAX +#define UINT_FAST32_MAX _PDCLIB_UINT_FAST32_MAX + +#define INT_FAST64_MIN _PDCLIB_INT_FAST64_MIN +#define INT_FAST64_MAX _PDCLIB_INT_FAST64_MAX +#define UINT_FAST64_MAX _PDCLIB_UINT_FAST64_MAX + +/* 7.18.2.4 Limits of integer types capable of holding object pointers */ + +#define INTPTR_MIN _PDCLIB_INTPTR_MIN +#define INTPTR_MAX _PDCLIB_INTPTR_MAX +#define UINTPTR_MAX _PDCLIB_UINTPTR_MAX + +/* 7.18.2.5 Limits of greatest-width integer types */ + +#define INTMAX_MIN _PDCLIB_INTMAX_MIN +#define INTMAX_MAX _PDCLIB_INTMAX_MAX +#define UINTMAX_MAX _PDCLIB_UINTMAX_MAX + +/* 7.18.3 Limits of other integer types */ + +#define PTRDIFF_MIN _PDCLIB_PTRDIFF_MIN +#define PTRDIFF_MAX _PDCLIB_PTRDIFF_MAX + +#define SIG_ATOMIC_MIN _PDCLIB_SIG_ATOMIC_MIN +#define SIG_ATOMIC_MAX _PDCLIB_SIG_ATOMIC_MAX + +#define SIZE_MAX _PDCLIB_SIZE_MAX + +#define WCHAR_MIN _PDCLIB_WCHAR_MIN +#define WCHAR_MAX _PDCLIB_WCHAR_MAX + +#define WINT_MIN _PDCLIB_WINT_MIN +#define WINT_MAX _PDCLIB_WINT_MAX + +#endif + +/* 7.18.4 Macros for integer constants */ + +#if defined( __cplusplus ) && __cplusplus < 201103L +#ifndef __STDC_CONSTANT_MACROS +#define _PDCLIB_NO_CONSTANT_MACROS +#endif +#endif + +#ifndef _PDCLIB_NO_CONSTANT_MACROS + +/* 7.18.4.1 Macros for minimum-width integer constants */ + +/* Expand to an integer constant of specified value and type int_leastN_t */ + +#define INT8_C _PDCLIB_INT_LEAST8_C +#define INT16_C _PDCLIB_INT_LEAST16_C +#define INT32_C _PDCLIB_INT_LEAST32_C +#define INT64_C _PDCLIB_INT_LEAST64_C + +/* Expand to an integer constant of specified value and type uint_leastN_t */ + +#define UINT8_C _PDCLIB_UINT_LEAST8_C +#define UINT16_C _PDCLIB_UINT_LEAST16_C +#define UINT32_C _PDCLIB_UINT_LEAST32_C +#define UINT64_C _PDCLIB_UINT_LEAST64_C + +/* 7.18.4.2 Macros for greatest-width integer constants */ + +/* Expand to an integer constant of specified value and type intmax_t */ +#define INTMAX_C( value ) _PDCLIB_INTMAX_C( value ) + +/* Expand to an integer constant of specified value and type uintmax_t */ +#define UINTMAX_C( value ) _PDCLIB_UINTMAX_C( value ) + +#endif + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 +#define RSIZE_MAX ( _PDCLIB_SIZE_MAX >> 1 ) +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDINT_H +#include _PDCLIB_EXTEND_STDINT_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/stdio.h b/libc/include/stdio.h new file mode 100644 index 0000000..4ee538f --- /dev/null +++ b/libc/include/stdio.h @@ -0,0 +1,935 @@ +/* Input/output + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDIO_H +#define _PDCLIB_STDIO_H _PDCLIB_STDIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_SIZE_T_DEFINED +#define _PDCLIB_SIZE_T_DEFINED _PDCLIB_SIZE_T_DEFINED +typedef _PDCLIB_size_t size_t; +#endif + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +/* See setvbuf(), third argument */ +#define _IOFBF (1u<<0) +#define _IOLBF (1u<<1) +#define _IONBF (1u<<2) + +/* The following are platform-dependent, and defined in _PDCLIB_config.h. */ +typedef struct _PDCLIB_fpos_t fpos_t; +typedef struct _PDCLIB_file_t FILE; +#define EOF (-1) +#define BUFSIZ _PDCLIB_BUFSIZ +#define FOPEN_MAX _PDCLIB_FOPEN_MAX +#define FILENAME_MAX _PDCLIB_FILENAME_MAX +#define L_tmpnam _PDCLIB_L_tmpnam +#define TMP_MAX _PDCLIB_TMP_MAX + +/* See fseek(), third argument */ +#define SEEK_CUR _PDCLIB_SEEK_CUR +#define SEEK_END _PDCLIB_SEEK_END +#define SEEK_SET _PDCLIB_SEEK_SET + +extern FILE * stdin; +extern FILE * stdout; +extern FILE * stderr; + +/* Operations on files */ + +/* Remove the given file. + Returns zero if successful, non-zero otherwise. + This implementation does detect if a file of that name is currently open, + and fails the remove in this case. This does not detect two distinct names + that merely result in the same file (e.g. "/home/user/foo" vs. "~/foo"). +*/ +_PDCLIB_PUBLIC int remove( const char * filename ); + +/* Rename the given old file to the given new name. + Returns zero if successful, non-zero otherwise. + This implementation does detect if the old filename corresponds to an open + file, and fails the rename in this case. + If there already is a file with the new filename, behaviour is defined by + the glue code (see functions/_PDCLIB/rename.c). +*/ +_PDCLIB_PUBLIC int rename( const char * oldpath, const char * newpath ); + +/* Open a temporary file with mode "wb+", i.e. binary-update. Remove the file + automatically if it is closed or the program exits normally (by returning + from main() or calling exit()). + Returns a pointer to a FILE handle for this file. + This implementation does not remove temporary files if the process aborts + abnormally (e.g. abort()). +*/ +_PDCLIB_PUBLIC FILE * tmpfile( void ); + +/* Generate a file name that is not equal to any existing filename AT THE TIME + OF GENERATION. Generate a different name each time it is called. + Returns a pointer to an internal static buffer containing the filename if s + is a NULL pointer. (This is not thread-safe!) + Returns s if it is not a NULL pointer (s is then assumed to point to an array + of at least L_tmpnam characters). + Returns NULL if unable to generate a suitable name (because all possible + names already exist, or the function has been called TMP_MAX times already). + Note that this implementation cannot guarantee a file of the name generated + is not generated between the call to this function and a subsequent fopen(). +*/ +_PDCLIB_PUBLIC char * tmpnam( char * s ); + +/* File access functions */ + +/* Close the file associated with the given stream (after flushing its buffers). + Returns zero if successful, EOF if any errors occur. +*/ +_PDCLIB_PUBLIC int fclose( FILE * stream ); + +/* Flush the buffers of the given output stream. If the stream is an input + stream, or an update stream with the last operation being an input operation, + behaviour is undefined. + If stream is a NULL pointer, perform the buffer flushing for all applicable + streams. + Returns zero if successful, EOF if a write error occurs. + Sets the error indicator of the stream if a write error occurs. +*/ +_PDCLIB_PUBLIC int fflush( FILE * stream ); + +/* Open the file with the given filename in the given mode, and return a stream + handle for it in which error and end-of-file indicator are cleared. Defined + values for mode are: + + READ MODES + text files binary files + without update "r" "rb" + with update "r+" "rb+" or "r+b" + + Opening in read mode fails if no file with the given filename exists, or if + cannot be read. + + WRITE MODES + text files binary files + without update "w" "wb" + with update "w+" "wb+" or "w+b" + + With write modes, if a file with the given filename already exists, it is + truncated to zero length. + + APPEND MODES + text files binary files + without update "a" "ab" + with update "a+" "ab+" or "a+b" + + With update modes, if a file with the given filename already exists, it is + not truncated to zero length, but all writes are forced to end-of-file (this + regardless to fseek() calls). Note that binary files opened in append mode + might have their end-of-file padded with '\0' characters. + + Update modes mean that both input and output functions can be performed on + the stream, but output must be terminated with a call to either fflush(), + fseek(), fsetpos(), or rewind() before input is performed, and input must + be terminated with a call to either fseek(), fsetpos(), or rewind() before + output is performed, unless input encountered end-of-file. + + If a text file is opened with update mode, the implementation is at liberty + to open a binary stream instead. This implementation honors the exact mode + given. + + The stream is fully buffered if and only if it can be determined not to + refer to an interactive device. + + If the mode string begins with but is longer than one of the above sequences + the implementation is at liberty to ignore the additional characters, or do + implementation-defined things. This implementation only accepts the exact + modes above. + + Returns a pointer to the stream handle if successfull, NULL otherwise. +*/ +_PDCLIB_PUBLIC FILE * fopen( const char * _PDCLIB_restrict filename, const char * _PDCLIB_restrict mode ); + +/* Close any file currently associated with the given stream. Open the file + identified by the given filename with the given mode (equivalent to fopen()), + and associate it with the given stream. If filename is a NULL pointer, + attempt to change the mode of the given stream. + This implementation allows any mode changes on "real" files, and associating + of the standard streams with files. It does *not* support mode changes on + standard streams. + (Primary use of this function is to redirect stdin, stdout, and stderr.) + + Returns a pointer to the stream handle if successfull, NULL otherwise. +*/ +_PDCLIB_PUBLIC FILE * freopen( const char * _PDCLIB_restrict filename, const char * _PDCLIB_restrict mode, FILE * _PDCLIB_restrict stream ); + +/* If buf is a NULL pointer, call setvbuf( stream, NULL, _IONBF, BUFSIZ ). + If buf is not a NULL pointer, call setvbuf( stream, buf, _IOFBF, BUFSIZ ). +*/ +_PDCLIB_PUBLIC void setbuf( FILE * _PDCLIB_restrict stream, char * _PDCLIB_restrict buf ); + +/* Set the given stream to the given buffering mode. If buf is not a NULL + pointer, use buf as file buffer (of given size). If buf is a NULL pointer, + use a buffer of given size allocated internally. _IONBF causes unbuffered + behaviour, _IOLBF causes line-buffered behaviour, _IOFBF causes fully + buffered behaviour. Calling this function is only valid right after a file is + opened, and before any other operation (except for any unsuccessful calls to + setvbuf()) has been performed. + Returns zero if successful, nonzero otherwise. +*/ +_PDCLIB_PUBLIC int setvbuf( FILE * _PDCLIB_restrict stream, char * _PDCLIB_restrict buf, int mode, size_t size ); + +/* Formatted input/output functions */ + +/* + Write output to the given stream, as defined by the given format string and + 0..n subsequent arguments (the argument stack). + + The format string is written to the given stream verbatim, except for any + conversion specifiers included, which start with the letter '%' and are + documented below. If the given conversion specifiers require more arguments + from the argument stack than provided, behaviour is undefined. Additional + arguments not required by conversion specifiers are evaluated but otherwise + ignored. + + (The standard specifies the format string is allowed to contain multibyte + character sequences as long as it starts and ends in initial shift state, + but this is not yet supported by this implementation, which interprets the + format string as sequence of char.) + TODO: Add multibyte support to printf() functions. + + A conversion specifier consists of: + - Zero or more flags (one of the characters "-+ #0"). + - Optional minimum field width as decimal integer. Default is padding to the + left, using spaces. Note that 0 is taken as a flag, not the beginning of a + field width. Note also that a small field width will not result in the + truncation of a value. + - Optional precision (given as ".#" with # being a decimal integer), + specifying: + - the min. number of digits to appear (diouxX), + - the max. number of digits after the decimal point (aAeEfF), + - the max. number of significant digits (gG), + - the max. number of bytes to be written (s). + - behaviour with other conversion specifiers is undefined. + - Optional length modifier specifying the size of the argument (one of "hh", + "ll", or one of the characters "hljztL"). + - Conversion specifier character specifying the type of conversion to be + applied (and the type of the next argument from the argument stack). One + of the characters "diouxXfFeEgGaAcspn%". + + Minimum field width and/or precision may be given as asterisk ('*') instead + of a decimal integer. In this case, the next argument from the argument + stack is assumed to be an int value specifying the width / precision. A + negative field width is interpreted as flag '-' followed by a positive field + width. A negative precision is interpreted as if no precision was given. + + FLAGS + - Left-justify the conversion result within its field width. + + Prefix a '+' on positive signed conversion results. Prefix a '-' on + floating conversions resulting in negative zero, or negative values + rounding to zero. + space Prefix a space on positive signed conversion results, or if a signed + conversion results in no characters. If both '+' and ' ' are given, + ' ' is ignored. + # Use an "alternative form" for + - 'o' conversion, increasing precision until the first digit of the + result is a zero; + - 'x' or 'X' conversion, prefixing "0x" or "0X" to nonzero results; + - "aAeEfF" conversions, always printing a decimal point even if no + digits are following; + - 'g' or 'G' conversions, always printing a decimal point even if no + digits are following, and not removing trailing zeroes. + - behaviour for other conversions is unspecified. + 0 Use leading zeroes instead of spaces for field width padding. If both + '-' and '0' are given, '0' is ignored. If a precision is specified for + any of the "diouxX" conversions, '0' is ignored. Behaviour is only + defined for "diouxXaAeEfFgG". + + LENGTH MODIFIERS + hh For "diouxX" conversions, the argument from the argument stack is + assumed to be of char width. (It will have been subject to integer + promotion but will be converted back.) For 'n' conversions, the argument + is assumed to be a pointer to signed char. + h For "diouxX" conversions, the argument from the argument stack is + assumed to be of short int width. (It will have been subject to integer + promotion but will be converted back.) For 'n' conversions, the argument + is assumed to be a pointer to short int. + l For "diouxX" conversions, the argument from the argument stack is + assumed to be of long int width. For 'n' conversions, the argument is + assumed to be a pointer to short int. For 'c' conversions, the argument + is assumed to be a wint_t. For 's' conversions, the argument is assumed + to be a pointer to wchar_t. No effect on "aAeEfFgG" conversions. + ll For "diouxX" conversions, the argument from the argument stack is + assumed to be of long long int width. For 'n' conversions, the argument + is assumed to be a pointer to long long int. + j For "diouxX" conversions, the argument from the argument stack is + assumed to be of intmax_t width. For 'n' conversions, the argument is + assumed to be a pointer to intmax_t. + z For "diouxX" conversions, the argument from the argument stack is + assumed to be of size_t width. For 'n' conversions, the argument is + assumed to be a pointer to size_t. + t For "diouxX" conversions, the argument from the argument stack is + assumed to be of ptrdiff_t width. For 'n' conversions, the argument is + assumed to be a pointer to ptrdiff_t. + L For "aAeEfFgG" conversions, the argument from the argument stack is + assumed to be a long double. + Length modifiers appearing for any conversions not mentioned above will have + undefined behaviour. + If a length modifier appears with any conversion specifier other than as + specified above, the behavior is undefined. + + CONVERSION SPECIFIERS + d,i The argument from the argument stack is assumed to be of type int, and + is converted to a signed decimal value with a minimum number of digits + as specified by the precision (default 1), padded with leading zeroes. + A zero value converted with precision zero yields no output. + o The argument from the argument stack is assumed to be of type unsigned + int, and is converted to an unsigned octal value, other behaviour being + as above. + u The argument from the argument stack is assumed to be of type unsigned + int, and converted to an unsigned decimal value, other behaviour being + as above. + x,X The argument from the argument stack is assumed to be of type unsigned + int, and converted to an unsigned hexadecimal value, using lowercase + "abcdef" for 'x' and uppercase "ABCDEF" for 'X' conversion, other + behaviour being as above. + f,F The argument from the argument stack is assumed to be of type double, + and converted to a decimal floating point in decimal-point notation, + with the number of digits after the decimal point as specified by the + precision (default 6) and the value being rounded appropriately. If + precision is zero (and the '#' flag is not given), no decimal point is + printed. At least one digit is always printed before the decimal point. + For 'f' conversions, an infinity value is printed as either [-]inf or + [-]infinity (, depending on the configuration of this implementation. A + NaN value is printed as [-]nan. For 'F' conversions uppercase characters + are used for these special values. The flags '-', '+' and ' ' apply as + usual to these special values, '#' and '0' have no effect. + e,E The argument from the argument stack is assumed to be of type double, + and converted to a decimal floating point in normalized exponential + notation ([?]d.ddd edd). "Normalized" means one nonzero digit before + the decimal point, unless the value is zero. The number of digits after + the decimal point is specified by the precision (default 6), the value + being rounded appropriately. If precision is zero (and the '#' flag is + not given), no decimal point is printed. The exponent has at least two + digits, and not more than necessary to represent the exponent. If the + value is zero, the exponent is zero. The 'e' written to indicate the + exponend is uppercase for 'E' conversions. + Infinity or NaN values are represented as for 'f' and 'F' conversions, + respectively. + g,G The argument from the argument stack is assumed to be of type double, + and converted according to either 'f' or 'e' format for 'g' conversions, + or 'F' or 'E' format for 'G' conversions, respectively, with the actual + conversion chosen depending on the value. 'e' / 'E' conversion is chosen + if the resulting exponent is < -4 or >= the precision (default 1). + Trailing zeroes are removed (unless the '#' flag is given). A decimal + point appears only if followed by a digit. + Infinity or NaN values are represented as for 'f' and 'F' conversions, + respectively. + a,A The argument from the argument stack is assumed to be of type double, + and converted to a floating point hexadecimal notation ([?]0xh.hhhh pd) + with one hexadecimal digit (being nonzero if the value is normalized, + and otherwise unspecified) before the decimal point, and the number of + digits after the decimal point being specified by the precision. If no + precision is given, the default is to print as many digits as nevessary + to give an exact representation of the value (if FLT_RADIX is a power of + 2). If no precision is given and FLT_RADIX is not a power of 2, the + default is to print as many digits to distinguish values of type double + (possibly omitting trailing zeroes). (A precision p is sufficient to + distinguish values of the source type if 16^p-1 > b^n where b is + FLT_RADIX and n is the number of digits in the significand (to base b) + of the source type. A smaller p might suffice depending on the + implementation's scheme for determining the digit to the left of the + decimal point.) The error has the correct sign for the current rounding + direction. + Unless the '#' flag is given, no decimal-point is given for zero + precision. + The 'a' conversion uses lowercase "abcdef", "0x" and 'p', the 'A' + conversion uppercase "ABCDEF", "0X" and 'P'. + The exponent always has at least one digit, and not more than necessary + to represent the decimal exponent of 2. If the value is zero, the + exponent is zero. + Infinity or NaN values are represented as for 'f' and 'F' conversions, + respectively. + Binary implementations are at liberty to chose the hexadecimal digit to + the left of the decimal point so that subsequent digits align to nibble + boundaries. + c The argument from the argument stack is assumed to be of type int, and + converted to a character after the value has been cast to unsigned char. + If the 'l' length modifier is given, the argument is assumed to be of + type wint_t, and converted as by a "%ls" conversion with no precision + and a pointer to a two-element wchar_t array, with the first element + being the wint_t argument and the second a '\0' wide character. + s The argument from the argument stack is assumed to be a char array (i.e. + pointer to char). Characters from that array are printed until a zero + byte is encountered or as many bytes as specified by a given precision + have been written. + If the l length modifier is given, the argument from the argument stack + is assumed to be a wchar_t array (i.e. pointer to wchar_t). Wide + characters from that array are converted to multibyte characters as by + calls to wcrtomb() (using a mbstate_t object initialized to zero prior + to the first conversion), up to and including the terminating null wide + character. The resulting multibyte character sequence is then printed up + to but not including the terminating null character. If a precision is + given, it specifies the maximum number of bytes to be written (including + shift sequences). If the given precision would require access to a wide + character one past the end of the array, the array shall contain a '\0' + wide character. In no case is a partial multibyte character written. + Redundant shift sequences may result if the multibyte characters have a + state-dependent encoding. + TODO: Clarify these statements regarding %ls. + p The argument from the argument stack is assumed to be a void pointer, + and converted to a sequence of printing characters in an implementation- + defined manner. + This implementation casts the pointer to type intptr_t, and prints the + value as if a %#x conversion specifier was given. + n The argument from the argument stack is assumed to be a pointer to a + signed integer, into which the number of characters written so far by + this call to fprintf is stored. The behaviour, should any flags, field + widths, or precisions be given is undefined. + % A verbatim '%' character is written. No argument is taken from the + argument stack. + + Returns the number of characters written if successful, a negative value + otherwise. +*/ +_PDCLIB_PUBLIC int fprintf( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, ... ); + +/* TODO: fscanf() documentation */ +/* + Read input from a given stream, as defined by the given format string, and + store converted input in the objects pointed to by 0..n subsequent arguments + (the argument stack). + + The format string contains a sequence of directives that are expected to + match the input. If such a directive fails to match, the function returns + (matching error). It also returns if an input error occurs (input error). + + Directives can be: + - one or more whitespaces, matching any number of whitespaces in the input; + - printing characters, matching the input verbatim; + - conversion specifications, which convert an input sequence into a value as + defined by the individual specifier, and store that value in a memory + location pointed to by the next pointer on the argument stack. Details are + documented below. If there is an insufficient number of pointers on the + argument stack, behaviour is undefined. Additional arguments not required + by any conversion specifications are evaluated, but otherwise ignored. + + (The standard specifies the format string is allowed to contain multibyte + character sequences as long as it starts and ends in initial shift state, + but this is not yet supported by this implementation, which interprets the + format string as sequence of char.) + TODO: Add multibyte support to scanf() functions. + + A conversion specifier consists of: + - Optional assignment-suppressing character ('*') that makes the conversion + read input as usual, but does not assign the conversion result. + - Optional maximum field width as decimal integer. + - Optional length modifier specifying the size of the argument (one of "hh", + "ll", or one of the characters "hljztL"). + - Conversion specifier character specifying the type of conversion to be + applied (and the type of the next argument from the argument stack). One + of the characters "diouxXaAeEfFgGcs[pn%". + + LENGTH MODIFIERS + hh For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of of char width. + h For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of short int width. + l For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of long int width. + For "aAeEfFgG" conversions, it is assumed to point to a variable of type + double. + For "cs[" conversions, it is assumed to point to a variable of type + wchar_t. + ll For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of long long int width. + j For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of intmax_t width. + z For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of size_t width. + t For "diouxXn" conversions, the next pointer from the argument stack is + assumed to point to a variable of ptrdiff_t width. + L For "aAeEfFgG" conversions, the next pointer from the argument stack is + assumed to point to a variable of type long double. + Length modifiers appearing for any conversions not mentioned above will have + undefined behaviour. + If a length modifier appears with any conversion specifier other than as + specified above, the behavior is undefined. + + CONVERSION SPECIFIERS + d Matches an (optionally signed) decimal integer of the format expected + by strtol() with base 10. The next pointer from the argument stack is + assumed to point to a signed integer. + i Matches an (optionally signed) integer of the format expected by + strtol() with base 0. The next pointer from the argument stack is + assumed to point to a signed integer. + o Matches an (optionally signed) octal integer of the format expected by + strtoul() with base 8. The next pointer from the argument stack is + assumed to point to an unsigned integer. + u Matches an (optionally signed) decimal integer of the format expected + by strtoul() with base 10. The next pointer from the argument stack is + assumed to point to an unsigned integer. + x Matches an (optionally signed) hexadecimal integer of the format + expected by strtoul() with base 16. The next pointer from the argument + stack is assumed to point to an unsigned integer. + aefg Matches an (optionally signed) floating point number, infinity, or not- + a-number-value of the format expected by strtod(). The next pointer + from the argument stack is assumed to point to a float. + c Matches a number of characters as specified by the field width (default + 1). The next pointer from the argument stack is assumed to point to a + character array large enough to hold that many characters. + If the 'l' length modifier is given, the input is assumed to match a + sequence of multibyte characters (starting in the initial shift state), + which will be converted to a wide character sequence as by successive + calls to mbrtowc() with a mbstate_t object initialized to zero prior to + the first conversion. The next pointer from the argument stack is + assumed to point to a wchar_t array large enough to hold that many + characters. + In either case, note that no '\0' character is added to terminate the + sequence. + s Matches a sequence of non-white-space characters. The next pointer from + the argument stack is assumed to point to a character array large + enough to hold the sequence including terminating '\0' character. + If the 'l' length modifier is given, the input is assumed to match a + sequence of multibyte characters (starting in the initial shift state), + which will be converted to a wide character sequence as by a call to + mbrtowc() with a mbstate_t object initialized to zero prior to the + first conversion. The next pointer from the argument stack is assumed + to point to a wchar_t array large enough to hold the sequence including + terminating '\0' character. + [ Matches a nonempty sequence consisting of any of those characters + specified between itself and a corresponding closing bracket (']'). + If the first character in the list is a circumflex ('^'), this matches + a nonempty sequence consisting of any characters NOT specified. If the + closing bracket appears as the first character in the scanset ("[]" or + "[^]", it is assumed to belong to the scanset, which then ends with the + NEXT closing bracket. + If there is a '-' character in the scanset which is not the first after + the opening bracket (or the circumflex, see above) or the last in the + scanset, behaviour is implementation-defined. This implementation + handles this character like any other. + + The extend of the input field is determined byte-by-byte for the above + conversions ('c', 's', '['), with no special provisions being made for + multibyte characters. The resulting field is nevertheless a multibyte + sequence begining in intial shift state. + + p Matches a sequence of characters as produced by the printf() "%p" + conversion. The next pointer from the argument stack is assumed to + point to a void pointer, which will be filled with the same location + as the pointer used in the printf() statement. Note that behaviour is + undefined if the input value is not the result of an earlier printf() + call. + n Does not read input. The next pointer from the argument stack is + assumed to point to a signed integer, into which the number of + characters read from input so far by this call to fscanf() is stored. + This does not affect the return value of fscanf(). The behaviour, + should an assignment-supressing character of field width be given, + is undefined. + This can be used to test the success of literal matches and suppressed + assignments. + % Matches a single, verbatim '%' character. + + A, E, F, G and X are valid, and equivalent to their lowercase counterparts. + + All conversions except [, c, or n imply that whitespace characters from the + input stream are consumed until a non-whitespace character is encountered. + Such whitespaces do not count against a maximum field width. + + Conversions push at most one character back into the input stream. That + implies that some character sequences converted by the strtol() and strtod() + function families are not converted identically by the scnaf() function + family. + + Returns the number of input items successfully assigned. This can be zero if + an early mismatch occurs. Returns EOF if an input failure occurs before the + first conversion. +*/ +_PDCLIB_PUBLIC int fscanf( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fprintf( stdout, format, ... ). */ +_PDCLIB_PUBLIC int printf( const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fscanf( stdin, format, ... ). */ +_PDCLIB_PUBLIC int scanf( const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fprintf( stdout, format, ... ), except that the result is + written into the buffer pointed to by s, instead of stdout, and that any + characters beyond the (n-1)th are discarded. The (n)th character is + replaced by a '\0' character in this case. + Returns the number of characters that would have been written (not counting + the terminating '\0' character) if n had been sufficiently large, if + successful, and a negative number if an encoding error ocurred. +*/ +_PDCLIB_PUBLIC int snprintf( char * _PDCLIB_restrict s, size_t n, const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fprintf( stdout, format, ... ), except that the result is + written into the buffer pointed to by s, instead of stdout. +*/ +_PDCLIB_PUBLIC int sprintf( char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fscanf( stdin, format, ... ), except that the input is read + from the buffer pointed to by s, instead of stdin. +*/ +_PDCLIB_PUBLIC int sscanf( const char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, ... ); + +/* Equivalent to fprintf( stream, format, ... ), except that the argument stack + is passed as va_list parameter. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vfprintf( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to fscanf( stream, format, ... ), except that the argument stack + is passed as va_list parameter. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vfscanf( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to fprintf( stdout, format, ... ), except that the argument stack + is passed as va_list parameter. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vprintf( const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to fscanf( stdin, format, ... ), except that the argument stack + is passed as va_list parameter. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vscanf( const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to snprintf( s, n, format, ... ), except that the argument stack + is passed as va_list parameter. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vsnprintf( char * _PDCLIB_restrict s, size_t n, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to fprintf( stdout, format, ... ), except that the argument stack + is passed as va_list parameter, and the result is written to the buffer + pointed to by s, instead of stdout. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vsprintf( char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Equivalent to fscanf( stdin, format, ... ), except that the argument stack + is passed as va_list parameter, and the input is read from the buffer + pointed to by s, instead of stdin. Note that va_list is not declared by + . +*/ +_PDCLIB_PUBLIC int vsscanf( const char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); + +/* Character input/output functions */ + +/* Retrieve the next character from given stream. + Returns the character, EOF otherwise. + If end-of-file is reached, the EOF indicator of the stream is set. + If a read error occurs, the error indicator of the stream is set. +*/ +_PDCLIB_PUBLIC int fgetc( FILE * stream ); + +/* Read at most n-1 characters from given stream into the array s, stopping at + \n or EOF. Terminate the read string with \n. If EOF is encountered before + any characters are read, leave the contents of s unchanged. + Returns s if successful, NULL otherwise. + If a read error occurs, the error indicator of the stream is set. In this + case, the contents of s are indeterminate. +*/ +_PDCLIB_PUBLIC char * fgets( char * _PDCLIB_restrict s, int n, FILE * _PDCLIB_restrict stream ); + +/* Write the value c (cast to unsigned char) to the given stream. + Returns c if successful, EOF otherwise. + If a write error occurs, sets the error indicator of the stream is set. +*/ +_PDCLIB_PUBLIC int fputc( int c, FILE * stream ); + +/* Write the string s (not including the terminating \0) to the given stream. + Returns a value >=0 if successful, EOF otherwise. + This implementation does set the error indicator of the stream if a write + error occurs. +*/ +_PDCLIB_PUBLIC int fputs( const char * _PDCLIB_restrict s, FILE * _PDCLIB_restrict stream ); + +/* Equivalent to fgetc( stream ), but may be overloaded by a macro that + evaluates its parameter more than once. +*/ +_PDCLIB_PUBLIC int getc( FILE * stream ); + +/* Equivalent to fgetc( stdin ). */ +_PDCLIB_PUBLIC int getchar( void ); + +/* Equivalent to fputc( c, stream ), but may be overloaded by a macro that + evaluates its parameter more than once. +*/ +_PDCLIB_PUBLIC int putc( int c, FILE * stream ); + +/* Equivalent to fputc( c, stdout ), but may be overloaded by a macro that + evaluates its parameter more than once. +*/ +_PDCLIB_PUBLIC int putchar( int c ); + +/* Write the string s (not including the terminating \0) to stdout, and append + a newline to the output. Returns a value >= 0 when successful, EOF if a + write error occurred. +*/ +_PDCLIB_PUBLIC int puts( const char * s ); + +/* Push the value c (cast to unsigned char) back onto the given (input) stream. + A character pushed back in this way will be delivered by subsequent read + operations (and skipped by subsequent file positioning operations) as if it + has not been read. The external representation of the stream is unaffected + by this pushback (it is a buffer operation). One character of pushback is + guaranteed, further pushbacks may fail. EOF as value for c does not change + the input stream and results in failure of the function. + For text files, the file position indicator is indeterminate until all + pushed-back characters are read. For binary files, the file position + indicator is decremented by each successful call of ungetc(). If the file + position indicator for a binary file was zero before the call of ungetc(), + behaviour is undefined. (Older versions of the library allowed such a call.) + Returns the pushed-back character if successful, EOF if it fails. +*/ +_PDCLIB_PUBLIC int ungetc( int c, FILE * stream ); + +/* Direct input/output functions */ + +/* Read up to nmemb elements of given size from given stream into the buffer + pointed to by ptr. Returns the number of elements successfully read, which + may be less than nmemb if a read error or EOF is encountered. If a read + error is encountered, the value of the file position indicator is + indeterminate. If a partial element is read, its value is indeterminate. + If size or nmemb are zero, the function does nothing and returns zero. +*/ +_PDCLIB_PUBLIC size_t fread( void * _PDCLIB_restrict ptr, size_t size, size_t nmemb, FILE * _PDCLIB_restrict stream ); + +/* Write up to nmemb elements of given size from buffer pointed to by ptr to + the given stream. Returns the number of elements successfully written, which + will be less than nmemb only if a write error is encountered. If a write + error is encountered, the value of the file position indicator is + indeterminate. If size or nmemb are zero, the function does nothing and + returns zero. +*/ +_PDCLIB_PUBLIC size_t fwrite( const void * _PDCLIB_restrict ptr, size_t size, size_t nmemb, FILE * _PDCLIB_restrict stream ); + +/* File positioning functions */ + +/* Store the current position indicator (and, where appropriate, the current + mbstate_t status object) for the given stream into the given pos object. The + actual contents of the object are unspecified, but it can be used as second + parameter to fsetpos() to reposition the stream to the exact position and + parse state at the time fgetpos() was called. + Returns zero if successful, nonzero otherwise. + TODO: Implementation-defined errno setting for fgetpos(). +*/ +_PDCLIB_PUBLIC int fgetpos( FILE * _PDCLIB_restrict stream, fpos_t * _PDCLIB_restrict pos ); + +/* Set the position indicator for the given stream to the given offset from: + - the beginning of the file if whence is SEEK_SET, + - the current value of the position indicator if whence is SEEK_CUR, + - end-of-file if whence is SEEK_END. + On text streams, non-zero offsets are only allowed with SEEK_SET, and must + have been returned by ftell() for the same file. + Any characters buffered by ungetc() are dropped, the end-of-file indicator + for the stream is cleared. If the given stream is an update stream, the next + operation after a successful fseek() may be either input or output. + Returns zero if successful, nonzero otherwise. If a read/write error occurs, + the error indicator for the given stream is set. +*/ +_PDCLIB_PUBLIC int fseek( FILE * stream, long int offset, int whence ); + +/* Set the position indicator (and, where appropriate the mbstate_t status + object) for the given stream to the given pos object (created by an earlier + call to fgetpos() on the same file). + Any characters buffered by ungetc() are dropped, the end-of-file indicator + for the stream is cleared. If the given stream is an update stream, the next + operation after a successful fsetpos() may be either input or output. + Returns zero if successful, nonzero otherwise. If a read/write error occurs, + the error indicator for the given stream is set. + TODO: Implementation-defined errno setting for fsetpos(). +*/ +_PDCLIB_PUBLIC int fsetpos( FILE * stream, const fpos_t * pos ); + +/* Return the current offset of the given stream from the beginning of the + associated file. For text streams, the exact value returned is unspecified + (and may not be equal to the number of characters), but may be used in + subsequent calls to fseek(). + Returns -1L if unsuccessful. + TODO: Implementation-defined errno setting for ftell(). +*/ +_PDCLIB_PUBLIC long int ftell( FILE * stream ); + +/* Equivalent to (void)fseek( stream, 0L, SEEK_SET ), except that the error + indicator for the stream is also cleared. +*/ +_PDCLIB_PUBLIC void rewind( FILE * stream ); + +/* Error-handling functions */ + +/* Clear the end-of-file and error indicators for the given stream. */ +_PDCLIB_PUBLIC void clearerr( FILE * stream ); + +/* Return zero if the end-of-file indicator for the given stream is not set, + nonzero otherwise. +*/ +_PDCLIB_PUBLIC int feof( FILE * stream ); + +/* Return zero if the error indicator for the given stream is not set, nonzero + otherwise. +*/ +_PDCLIB_PUBLIC int ferror( FILE * stream ); + +/* If s is neither a NULL pointer nor an empty string, print the string to + stderr (with appended colon (':') and a space) first. In any case, print an + error message depending on the current value of errno (being the same as if + strerror( errno ) had been called). +*/ +_PDCLIB_PUBLIC void perror( const char * s ); + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 + +#define L_tmpnam_s _PDCLIB_L_tmpnam +#define TMP_MAX_S _PDCLIB_TMP_MAX + +#ifndef _PDCLIB_ERRNO_T_DEFINED +#define _PDCLIB_ERRNO_T_DEFINED _PDCLIB_ERRNO_T_DEFINED +typedef int errno_t; +#endif + +#ifndef _PDCLIB_RSIZE_T_DEFINED +#define _PDCLIB_RSIZE_T_DEFINED _PDCLIB_RSIZE_T_DEFINED +typedef _PDCLIB_size_t rsize_t; +#endif + +/* Open a temporary file with mode "wb+", i.e. binary-update. Remove the file + automatically if it is closed or the program exits normally (by returning + from main() or calling exit()). + If successful, the FILE * pointed to by streamptr will be set to point at + the opened file handle, and the function returns zero. If unsuccessful, + the FILE * pointed to by streamptr will be set to NULL and a non-zero + value is returned. + The following conditions will be considered runtime constraint violations: + - streamptr being NULL. + In case of a constraint violation, no file is being created. + This implementation does not remove temporary files if the process aborts + abnormally (e.g. abort()). +*/ +_PDCLIB_PUBLIC errno_t tmpfile_s( FILE * _PDCLIB_restrict * _PDCLIB_restrict streamptr ); + +/* Open the file with the given filename in the given mode, and sets the given + streamptr to point at the file handle for that file, in which error and + end-of-file indicator are cleared. Defined values for mode are: + + READ MODES + text files binary files + without update "r" "rb" + with update "r+" "rb+" or "r+b" + + Opening in read mode fails if no file with the given filename exists, or if + cannot be read. + + WRITE MODES + text files binary files + without update "w" "wb" + with update "w+" "wb+" or "w+b" + + With write modes, if a file with the given filename already exists, it is + truncated to zero length. + + APPEND MODES + text files binary files + without update "a" "ab" + with update "a+" "ab+" or "a+b" + + With update modes, if a file with the given filename already exists, it is + not truncated to zero length, but all writes are forced to end-of-file (this + regardless to fseek() calls). Note that binary files opened in append mode + might have their end-of-file padded with '\0' characters. + + Update modes mean that both input and output functions can be performed on + the stream, but output must be terminated with a call to either fflush(), + fseek(), fsetpos(), or rewind() before input is performed, and input must + be terminated with a call to either fseek(), fsetpos(), or rewind() before + output is performed, unless input encountered end-of-file. + + If a text file is opened with update mode, the implementation is at liberty + to open a binary stream instead. This implementation honors the exact mode + given. + + The stream is fully buffered if and only if it can be determined not to + refer to an interactive device. + + If the mode string begins with but is longer than one of the above sequences + the implementation is at liberty to ignore the additional characters, or do + implementation-defined things. This implementation only accepts the exact + modes above. + + The following conditions will be considered runtime constraint violations: + - streamptr being NULL. + - filename being NULL. + - mode being NULL. + In case of a constraint violation, no file is opened. If streamptr is not + NULL, *streamptr is set to NULL. + + Returns zero if successful, non-zero otherwise. +*/ +_PDCLIB_PUBLIC errno_t fopen_s( FILE * _PDCLIB_restrict * _PDCLIB_restrict streamptr, const char * _PDCLIB_restrict filename, const char * _PDCLIB_restrict mode ); + +/* Close any file currently associated with the given stream. Open the file + identified by the given filename with the given mode (equivalent to fopen()), + and associate it with the given stream. If filename is a NULL pointer, + attempt to change the mode of the given stream. + This implementation allows any mode changes on "real" files, and associating + of the standard streams with files. It does *not* support mode changes on + standard streams. + (Primary use of this function is to redirect stdin, stdout, and stderr.) + + The following conditions will be considered runtime constraint violations: + - newstreamptr being NULL. + - mode being NULL. + - stream being NULL. + In case of a constraint violation, no attempt is made to close or open any + file. If newstreamptr is not NULL, *newstreamptr is set to NULL. + + Returns zero if successfull, non-zero otherwise. +*/ +_PDCLIB_PUBLIC errno_t freopen_s( FILE * _PDCLIB_restrict * _PDCLIB_restrict newstreamptr, const char * _PDCLIB_restrict filename, const char * _PDCLIB_restrict mode, FILE * _PDCLIB_restrict stream ); + +/* None of these are implemented yet. Placeholder declarations. */ +_PDCLIB_PUBLIC errno_t tmpnam_s( char * s, rsize_t maxsize ); +_PDCLIB_PUBLIC int fprintf_s( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int fscanf_s( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int printf_s( const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int scanf_s( const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int snprintf_s( char * _PDCLIB_restrict s, rsize_t n, const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int sprintf_s( char * _PDCLIB_restrict s, rsize_t n, const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int sscanf_s( const char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, ... ); +_PDCLIB_PUBLIC int vfprintf_s( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vfscanf_s( FILE * _PDCLIB_restrict stream, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vprintf_s( const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vscanf_s( const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vsnprintf_s( char * _PDCLIB_restrict s, rsize_t n, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vsprintf_s( char * _PDCLIB_restrict s, rsize_t n, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC int vsscanf_s( const char * _PDCLIB_restrict s, const char * _PDCLIB_restrict format, _PDCLIB_va_list arg ); +_PDCLIB_PUBLIC char * gets_s( char * s, rsize_t n ); + +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDIO_H +#include _PDCLIB_EXTEND_STDIO_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/stdlib.h b/libc/include/stdlib.h new file mode 100644 index 0000000..9dc8b33 --- /dev/null +++ b/libc/include/stdlib.h @@ -0,0 +1,379 @@ +/* General utilities + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDLIB_H +#define _PDCLIB_STDLIB_H _PDCLIB_STDLIB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_SIZE_T_DEFINED +#define _PDCLIB_SIZE_T_DEFINED _PDCLIB_SIZE_T_DEFINED +typedef _PDCLIB_size_t size_t; +#endif + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +/* Numeric conversion functions */ + +/* TODO: atof(), strtof(), strtod(), strtold() */ + +_PDCLIB_PUBLIC double atof( const char * nptr ); +_PDCLIB_PUBLIC double strtod( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr ); +_PDCLIB_PUBLIC float strtof( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr ); +_PDCLIB_PUBLIC long double strtold( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr ); + +/* Separate the character array nptr into three parts: A (possibly empty) + sequence of whitespace characters, a character representation of an integer + to the given base, and trailing invalid characters (including the terminating + null character). If base is 0, assume it to be 10, unless the integer + representation starts with 0x / 0X (setting base to 16) or 0 (setting base to + 8). If given, base can be anything from 0 to 36, using the 26 letters of the + base alphabet (both lowercase and uppercase) as digits 10 through 35. + The integer representation is then converted into the return type of the + function. It can start with a '+' or '-' sign. If the sign is '-', the result + of the conversion is negated. + If the conversion is successful, the converted value is returned. If endptr + is not a NULL pointer, a pointer to the first trailing invalid character is + returned in *endptr. + If no conversion could be performed, zero is returned (and nptr in *endptr, + if endptr is not a NULL pointer). If the converted value does not fit into + the return type, the functions return LONG_MIN, LONG_MAX, ULONG_MAX, + LLONG_MIN, LLONG_MAX, or ULLONG_MAX respectively, depending on the sign of + the integer representation and the return type, and errno is set to ERANGE. +*/ +/* There is strtoimax() and strtoumax() in operating on intmax_t / + uintmax_t, if the long long versions do not suit your needs. +*/ +_PDCLIB_PUBLIC long int strtol( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); +_PDCLIB_PUBLIC long long int strtoll( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); +_PDCLIB_PUBLIC unsigned long int strtoul( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); +_PDCLIB_PUBLIC unsigned long long int strtoull( const char * _PDCLIB_restrict nptr, char ** _PDCLIB_restrict endptr, int base ); + +/* These functions are the equivalent of (int)strtol( nptr, NULL, 10 ), + strtol( nptr, NULL, 10 ) and strtoll(nptr, NULL, 10 ) respectively, with the + exception that they do not have to handle overflow situations in any defined + way. + (PDCLib does not simply forward these to their strtox() equivalents, but + provides a simpler atox() function that saves a couple of tests and simply + continues with the conversion in case of overflow.) +*/ +_PDCLIB_PUBLIC int atoi( const char * nptr ); +_PDCLIB_PUBLIC long int atol( const char * nptr ); +_PDCLIB_PUBLIC long long int atoll( const char * nptr ); + +/* Pseudo-random sequence generation functions */ + +extern unsigned long int _PDCLIB_seed; + +#define RAND_MAX 32767 + +/* Returns the next number in a pseudo-random sequence, which is between 0 and + RAND_MAX. + (PDCLib uses the implementation suggested by the standard document, which is + next = next * 1103515245 + 12345; return (unsigned int)(next/65536) % 32768;) +*/ +_PDCLIB_PUBLIC int rand( void ); + +/* Initialize a new pseudo-random sequence with the starting seed. Same seeds + result in the same pseudo-random sequence. The default seed is 1. +*/ +_PDCLIB_PUBLIC void srand( unsigned int seed ); + +/* Memory management functions */ + +/* Allocate a chunk of heap memory of given size. If request could not be + satisfied, return NULL. Otherwise, return a pointer to the allocated + memory. Memory contents are undefined. +*/ +_PDCLIB_PUBLIC void * malloc( size_t size ); + +/* Allocate a chunk of heap memory that is large enough to hold nmemb elements + of the given size, and zero-initialize that memory. If request could not be + satisfied, return NULL. Otherwise, return a pointer to the allocated + memory. +*/ +_PDCLIB_PUBLIC void * calloc( size_t nmemb, size_t size ); + +/* De-allocate a chunk of heap memory previously allocated using malloc(), + calloc(), or realloc(), and pointed to by ptr. If ptr does not match a + pointer previously returned by the mentioned allocation functions, or + free() has already been called for this ptr, behaviour is undefined. +*/ +_PDCLIB_PUBLIC void free( void * ptr ); + +/* Resize a chunk of memory previously allocated with malloc() and pointed to + by ptr to the given size (which might be larger or smaller than the original + size). Returns a pointer to the reallocated memory, or NULL if the request + could not be satisfied. Note that the resizing might include a memcpy() + from the original location to a different one, so the return value might or + might not equal ptr. If size is larger than the original size, the value of + memory beyond the original size is undefined. If ptr is NULL, realloc() + behaves like malloc(). +*/ +_PDCLIB_PUBLIC void * realloc( void * ptr, size_t size ); + +/* Communication with the environment */ + +/* These two can be passed to exit() or _Exit() as status values, to signal + successful and unsuccessful program termination, respectively. EXIT_SUCCESS + can be replaced by 0. How successful or unsuccessful program termination are + signaled to the environment, and what happens if exit() or _Exit() are being + called with a value that is neither of the three, is defined by the hosting + OS and its glue function. +*/ +#define EXIT_SUCCESS _PDCLIB_SUCCESS +#define EXIT_FAILURE _PDCLIB_FAILURE + +/* Initiate abnormal process termination, unless programm catches SIGABRT and + does not return from the signal handler. + This implementantion flushes all streams, closes all files, and removes any + temporary files before exiting with EXIT_FAILURE. + abort() does not return. +*/ +_PDCLIB_PUBLIC _PDCLIB_Noreturn void abort( void ) _PDCLIB_NORETURN; + +/* Register a function that will be called on quick_exit(). + At least 32 functions can be registered this way, and will be called in + reverse order of registration (last-in, first-out). + Returns zero if registration is successfull, nonzero if it failed. +*/ +_PDCLIB_PUBLIC int at_quick_exit( void ( *func )( void ) ); + +/* Register a function that will be called on exit(), or when main() returns. + At least 32 functions can be registered this way, and will be called in + reverse order of registration (last-in, first-out). + Returns zero if registration is successfull, nonzero if it failed. +*/ +_PDCLIB_PUBLIC int atexit( void ( *func )( void ) ); + +/* Normal process termination. Functions registered by atexit() (see above) are + called, streams flushed, files closed and temporary files removed before the + program is terminated with the given status. (See comment for EXIT_SUCCESS + and EXIT_FAILURE above.) + exit() does not return. +*/ +_PDCLIB_PUBLIC _PDCLIB_Noreturn void exit( int status ) _PDCLIB_NORETURN; + +/* Normal process termination. Functions registered by at_quick_exit() (see + above) are called, streams flushed, files closed and temporary files removed + before the program is terminated with the given status. (See comment for + EXIT_SUCCESS and EXIT_FAILURE above.) + quick_exit() does not return. +*/ +_PDCLIB_PUBLIC _PDCLIB_Noreturn void quick_exit( int status ) _PDCLIB_NORETURN; + +/* Normal process termination. Functions registered by atexit()/at_quick_exit() + (see above) are NOT CALLED. This implementation DOES flush streams, close + files and removes temporary files before the program is teminated with the + given status. (See comment for EXIT_SUCCESS and EXIT_FAILURE above.) + _Exit() does not return. +*/ +_PDCLIB_PUBLIC _PDCLIB_Noreturn void _Exit( int status ) _PDCLIB_NORETURN; + +/* Search an environment-provided key-value map for the given key name, and + return a pointer to the associated value string (or NULL if key name cannot + be found). The value string pointed to might be overwritten by a subsequent + call to getenv(). The library never calls getenv() itself. + Details on the provided keys and how to set / change them are determined by + the hosting OS and its glue function. +*/ +_PDCLIB_PUBLIC char * getenv( const char * name ); + +/* If string is a NULL pointer, system() returns nonzero if a command processor + is available, and zero otherwise. If string is not a NULL pointer, it is + passed to the command processor. If system() returns, it does so with a + value that is determined by the hosting OS and its glue function. +*/ +_PDCLIB_PUBLIC int system( const char * string ); + +/* Searching and sorting */ + +/* Do a binary search for a given key in the array with a given base pointer, + which consists of nmemb elements that are of the given size each. To compare + the given key with an element from the array, the given function compar is + called (with key as first parameter and a pointer to the array member as + second parameter); the function should return a value less than, equal to, + or greater than 0 if the key is considered to be less than, equal to, or + greater than the array element, respectively. + The function returns a pointer to a matching element found, or NULL if no + match is found. +*/ +_PDCLIB_PUBLIC void * bsearch( const void * key, const void * base, size_t nmemb, size_t size, int ( *compar )( const void *, const void * ) ); + +/* Do a quicksort on an array with a given base pointer, which consists of + nmemb elements that are of the given size each. To compare two elements from + the array, the given function compar is called, which should return a value + less than, equal to, or greater than 0 if the first argument is considered + to be less than, equal to, or greater than the second argument, respectively. + If two elements are compared equal, their order in the sorted array is not + specified. +*/ +_PDCLIB_PUBLIC void qsort( void * base, size_t nmemb, size_t size, int ( *compar )( const void *, const void * ) ); + +/* Integer arithmetic functions */ + +/* Return the absolute value of the argument. Note that on machines using two- + complement's notation (most modern CPUs), the largest negative value cannot + be represented as positive value. In this case, behaviour is unspecified. +*/ +_PDCLIB_PUBLIC int abs( int j ); +_PDCLIB_PUBLIC long int labs( long int j ); +_PDCLIB_PUBLIC long long int llabs( long long int j ); + +/* These structures each have a member quot and a member rem, of type int (for + div_t), long int (for ldiv_t) and long long it (for lldiv_t) respectively. + The order of the members is platform-defined to allow the div() functions + below to be implemented efficiently. +*/ +typedef struct _PDCLIB_div_t div_t; +typedef struct _PDCLIB_ldiv_t ldiv_t; +typedef struct _PDCLIB_lldiv_t lldiv_t; + +/* Return quotient (quot) and remainder (rem) of an integer division in one of + the structs above. +*/ +_PDCLIB_PUBLIC div_t div( int numer, int denom ); +_PDCLIB_PUBLIC ldiv_t ldiv( long int numer, long int denom ); +_PDCLIB_PUBLIC lldiv_t lldiv( long long int numer, long long int denom ); + +/* TODO: Multibyte / wide character conversion functions */ + +/* TODO: Macro MB_CUR_MAX */ + +/* +_PDCLIB_PUBLIC int mblen( const char * s, size_t n ); +_PDCLIB_PUBLIC int mbtowc( wchar_t * _PDCLIB_restrict pwc, const char * _PDCLIB_restrict s, size_t n ); +_PDCLIB_PUBLIC int wctomb( char * s, wchar_t wc ); +_PDCLIB_PUBLIC size_t mbstowcs( wchar_t * _PDCLIB_restrict pwcs, const char * _PDCLIB_restrict s, size_t n ); +_PDCLIB_PUBLIC size_t wcstombs( char * _PDCLIB_restrict s, const wchar_t * _PDCLIB_restrict pwcs, size_t n ); +*/ + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 + +#ifndef _PDCLIB_ERRNO_T_DEFINED +#define _PDCLIB_ERRNO_T_DEFINED _PDCLIB_ERRNO_T_DEFINED +typedef int errno_t; +#endif + +#ifndef _PDCLIB_RSIZE_T_DEFINED +#define _PDCLIB_RSIZE_T_DEFINED _PDCLIB_RSIZE_T_DEFINED +typedef size_t rsize_t; +#endif + +/* A function type that can serve as a constraint handler (see below). The + first parameter is an error message on the constraint violation, the + second parameter a pointer to an implementation-defined object, the + third an error code related to the constraint violation. + If the function calling the constraint handler is defined to return + errno_t, the third parameter will be identical to the return value of + that function. + This implementation sets the second parameter of the constraint handler + call to NULL. +*/ +typedef void ( *constraint_handler_t )( const char * _PDCLIB_restrict msg, void * _PDCLIB_restrict ptr, errno_t error ); + +/* The currently active constraint violation handler. This implementation + sets abort_handler_s as the default constraint violation handler. +*/ +extern constraint_handler_t _PDCLIB_constraint_handler; + +/* Set the given function as the new constraint violation handler. */ +_PDCLIB_PUBLIC constraint_handler_t set_constraint_handler_s( constraint_handler_t handler ); + +/* One of two predefined constraint violation handlers. When called, it will + print an error message (including the message passed as the first + parameter to the handler function) and call abort(). +*/ +_PDCLIB_PUBLIC void abort_handler_s( const char * _PDCLIB_restrict msg, void * _PDCLIB_restrict ptr, errno_t error ); + +/* One of two predefined constraint violation handlers. Simply returns, + ignoring the constraint violation. +*/ +_PDCLIB_PUBLIC void ignore_handler_s( const char * _PDCLIB_restrict msg, void * _PDCLIB_restrict ptr, errno_t error ); + +/* Search an environment-provided key-value map for the given key name. + If the name is found, + - if len is not NULL, the length of the associated value string is stored + in that location. + - if len < maxsize, the value string is copied to the indicated location. + If the name is not found, + - if len is not NULL, a zero is stored in that location. + - if maxsize > 0, value[0] is set to the null character. + Details on the provided keys and how to set / change them are determined by + the hosting OS and its glue function. + The following conditions will be considered runtime constraint violations: + - value being a NULL pointer. + - maxsize == 0 or maxsize > RSIZE_MAX. + In case of a constraint violation, if len is not NULL a zero will be + stored at that location, and the environment key-value map not searched. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t getenv_s( size_t * _PDCLIB_restrict len, char * _PDCLIB_restrict value, rsize_t maxsize, const char * _PDCLIB_restrict name ); + +/* Do a binary search for a given key in the array with a given base pointer, + which consists of nmemb elements that are of the given size each. To compare + the given key with an element from the array, the given function compar is + called (with key as first parameter, a pointer to the array member as + second parameter, and the context parameter passed to bsearch_s() as third + parameter); the function should return a value less than, equal to, + or greater than 0 if the key is considered to be less than, equal to, or + greater than the array element, respectively. + The function returns a pointer to a matching element found, or NULL if no + match is found. + The following conditions will be considered runtime constraint violations: + - nmemb or size > RSIZE_MAX. + - nmemb > 0 and either of key, base, or compar being a null pointer. + In case of a constraint violation, the array will not be searched. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC void * bsearch_s( const void * key, const void * base, rsize_t nmemb, rsize_t size, int ( *compar )( const void * k, const void * y, void * context ), void * context ); + +/* Do a quicksort on an array with a given base pointer, which consists of + nmemb elements that are of the given size each. To compare two elements from + the array, the given function compar is called, with the first two arguments + being pointers to the two objects to be compared, and the third argument + being the context parameter passed to qsort_s. The compar function should + return a value less than, equal to, or greater than 0 if the first argument + is considered to be less than, equal to, or greater than the second argument, + respectively. If two elements are compared equal, their order in the sorted + array is not specified. + The following conditions will be considered runtime constraint violations: + - nmemb or size > RSIZE_MAX. + - nmemb > 0 and either of base or compar being a null pointer. + In case of a constraint violation, the array will not be sorted. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t qsort_s( void * base, rsize_t nmemb, rsize_t size, int ( *compar )( const void * x, const void * y, void * context ), void * context ); + +/* TODO: Multibyte / wide character functions */ + +#endif + +#ifdef __cplusplus +} +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDLIB_H +#include _PDCLIB_EXTEND_STDLIB_H +#endif + +#endif diff --git a/libc/include/stdnoreturn.h b/libc/include/stdnoreturn.h new file mode 100644 index 0000000..ab4ddb4 --- /dev/null +++ b/libc/include/stdnoreturn.h @@ -0,0 +1,26 @@ +/* _Noreturn + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STDNORETURN_H +#define _PDCLIB_STDNORETURN_H _PDCLIB_STDNORETURN_H + +#include "pdclib/_PDCLIB_internal.h" + +/* This basically breaks the letter of the standard (which states that + noreturn be defined to _Noreturn). This defines noreturn -> _Noreturn + on C11 compliant compilers only (as older compilers do not know about + _Noreturn). +*/ +#define noreturn _PDCLIB_Noreturn + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STDNORETURN_H +#include _PDCLIB_EXTEND_STDNORETURN_H +#endif + +#endif diff --git a/libc/include/string.h b/libc/include/string.h new file mode 100644 index 0000000..05062fd --- /dev/null +++ b/libc/include/string.h @@ -0,0 +1,394 @@ +/* String handling + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_STRING_H +#define _PDCLIB_STRING_H _PDCLIB_STRING_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_SIZE_T_DEFINED +#define _PDCLIB_SIZE_T_DEFINED _PDCLIB_SIZE_T_DEFINED +typedef _PDCLIB_size_t size_t; +#endif + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +/* String function conventions */ + +/* + In any of the following functions taking a size_t n to specify the length of + an array or size of a memory region, n may be 0, but the pointer arguments to + the call shall still be valid unless otherwise stated. +*/ + +/* Copying functions */ + +/* Copy a number of n characters from the memory area pointed to by s2 to the + area pointed to by s1. If the two areas overlap, behaviour is undefined. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC void * memcpy( void * _PDCLIB_restrict s1, const void * _PDCLIB_restrict s2, size_t n ); + +/* Copy a number of n characters from the memory area pointed to by s2 to the + area pointed to by s1. The two areas may overlap. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC void * memmove( void * _PDCLIB_restrict s1, const void * _PDCLIB_restrict s2, size_t n ); + +/* Copy the character array s2 (including terminating '\0' byte) into the + character array s1. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC char * strcpy( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2 ); + +/* Copy a maximum of n characters from the character array s2 into the character + array s1. If s2 is shorter than n characters, '\0' bytes will be appended to + the copy in s1 until n characters have been written. If s2 is longer than n + characters, NO terminating '\0' will be written to s1. If the arrays overlap, + behaviour is undefined. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC char * strncpy( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2, size_t n ); + +/* Concatenation functions */ + +/* Append the contents of the character array s2 (including terminating '\0') to + the character array s1 (first character of s2 overwriting the '\0' of s1). If + the arrays overlap, behaviour is undefined. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC char * strcat( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2 ); + +/* Append a maximum of n characters from the character array s2 to the character + array s1 (first character of s2 overwriting the '\0' of s1). A terminating + '\0' is ALWAYS appended, even if the full n characters have already been + written. If the arrays overlap, behaviour is undefined. + Returns the value of s1. +*/ +_PDCLIB_PUBLIC char * strncat( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2, size_t n ); + +/* Comparison functions */ + +/* Compare the first n characters of the memory areas pointed to by s1 and s2. + Returns 0 if s1 == s2, a negative number if s1 < s2, and a positive number if + s1 > s2. +*/ +_PDCLIB_PUBLIC int memcmp( const void * s1, const void * s2, size_t n ); + +/* Compare the character arrays s1 and s2. + Returns 0 if s1 == s2, a negative number if s1 < s2, and a positive number if + s1 > s2. +*/ +_PDCLIB_PUBLIC int strcmp( const char * s1, const char * s2 ); + +/* Compare the character arrays s1 and s2, interpreted as specified by the + LC_COLLATE category of the current locale. + Returns 0 if s1 == s2, a negative number if s1 < s2, and a positive number if + s1 > s2. + TODO: Currently a dummy wrapper for strcmp() as PDCLib does not yet support + locales. +*/ +_PDCLIB_PUBLIC int strcoll( const char * s1, const char * s2 ); + +/* Compare no more than the first n characters of the character arrays s1 and + s2. + Returns 0 if s1 == s2, a negative number if s1 < s2, and a positive number if + s1 > s2. +*/ +_PDCLIB_PUBLIC int strncmp( const char * s1, const char * s2, size_t n ); + +/* Transform the character array s2 as appropriate for the LC_COLLATE setting of + the current locale. If length of resulting string is less than n, store it in + the character array pointed to by s1. Return the length of the resulting + string. +*/ +_PDCLIB_PUBLIC size_t strxfrm( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2, size_t n ); + +/* Search functions */ + +/* Search the first n characters in the memory area pointed to by s for the + character c (interpreted as unsigned char). + Returns a pointer to the first instance found, or NULL. +*/ +_PDCLIB_PUBLIC void * memchr( const void * s, int c, size_t n ); + +/* Search the character array s (including terminating '\0') for the character c + (interpreted as char). + Returns a pointer to the first instance found, or NULL. +*/ +_PDCLIB_PUBLIC char * strchr( const char * s, int c ); + +/* Determine the length of the initial substring of character array s1 which + consists only of characters not from the character array s2. + Returns the length of that substring. +*/ +_PDCLIB_PUBLIC size_t strcspn( const char * s1, const char * s2 ); + +/* Search the character array s1 for any character from the character array s2. + Returns a pointer to the first occurrence, or NULL. +*/ +_PDCLIB_PUBLIC char * strpbrk( const char * s1, const char * s2 ); + +/* Search the character array s (including terminating '\0') for the character c + (interpreted as char). + Returns a pointer to the last instance found, or NULL. +*/ +_PDCLIB_PUBLIC char * strrchr( const char * s, int c ); + +/* Determine the length of the initial substring of character array s1 which + consists only of characters from the character array s2. + Returns the length of that substring. +*/ +_PDCLIB_PUBLIC size_t strspn( const char * s1, const char * s2 ); + +/* Search the character array s1 for the substring in character array s2. + Returns a pointer to that sbstring, or NULL. If s2 is of length zero, + returns s1. +*/ +_PDCLIB_PUBLIC char * strstr( const char * s1, const char * s2 ); + +/* In a series of subsequent calls, parse a C string into tokens. + On the first call to strtok(), the first argument is a pointer to the to-be- + parsed C string. On subsequent calls, the first argument is NULL unless you + want to start parsing a new string. s2 holds an array of separator characters + which can differ from call to call. Leading separators are skipped, the first + trailing separator overwritten with '\0'. + Returns a pointer to the next token. + WARNING: This function uses static storage, and as such is not reentrant. +*/ +_PDCLIB_PUBLIC char * strtok( char * _PDCLIB_restrict s1, const char * _PDCLIB_restrict s2 ); + +/* Miscellaneous functions */ + +/* Write the character c (interpreted as unsigned char) to the first n + characters of the memory area pointed to by s. + Returns s. +*/ +_PDCLIB_PUBLIC void * memset( void * s, int c, size_t n ); + +/* Map an error number to a (locale-specific) error message string. Error + numbers are typically errno values, but any number is mapped to a message. + TODO: PDCLib does not yet support locales. +*/ +_PDCLIB_PUBLIC char * strerror( int errnum ); + +/* Returns the length of the string s (excluding terminating '\0'). +*/ +_PDCLIB_PUBLIC size_t strlen( const char * s ); + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 + +#ifndef _PDCLIB_ERRNO_T_DEFINED +#define _PDCLIB_ERRNO_T_DEFINED _PDCLIB_ERRNO_T_DEFINED +typedef int errno_t; +#endif + +#ifndef _PDCLIB_RSIZE_T_DEFINED +#define _PDCLIB_RSIZE_T_DEFINED _PDCLIB_RSIZE_T_DEFINED +typedef _PDCLIB_size_t rsize_t; +#endif + +/* Copy a number of n characters from the memory area pointed to by s2 to the + area pointed to by s1 of size s1max. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max or n being > RSIZE_MAX. + - n > s1max (not enough space in s1). + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max <= RSIZE_MAX + then the first s1max characters of s1 will be set to zero. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t memcpy_s( void * _PDCLIB_restrict s1, rsize_t s1max, const void * _PDCLIB_restrict s2, rsize_t n ); + +/* Copy a number of n characters from the memory area pointed to by s2 to the + area pointed to by s1 of size s1max. The two areas may overlap. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max or n being > RSIZE_MAX. + - n > s1max (not enough space in s1). + In case of a constraint violation, if s1 is not NULL and s1max <= RSIZE_MAX + then the first s1max characters of s1 will be set to zero. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t memmove_s( void * _PDCLIB_restrict s1, rsize_t s1max, const void * _PDCLIB_restrict s2, rsize_t n ); + +/* Copy the character array s2 (including terminating '\0' byte) into the + character array s1. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max being zero or > RSIZE_MAX. + - s1max not greater than strnlen_s( s2, s1max ) (not enough space in s1). + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max <= RSIZE_MAX + then s1[0] will be set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t strcpy_s( char * _PDCLIB_restrict s1, rsize_t s1max, const char * _PDCLIB_restrict s2 ); + +/* Copy a maximum of n characters from the character array s2 into the character + array s1. If s2 is longer than n, s1[n] will be set to '\0'. + Returns zero if successful, non-zero otherwise. + + ATTENTION ATTENTION ATTENTION + + This function differs in two fundamental ways from strncpy(): + - remaining space in s1 will NOT be zeroed. Their value is unspecified. + - s1 WILL be zero-terminated even if there is not enough space to hold + all n characters from s2. + + THANK YOU FOR YOUR ATTENTION. + + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max or n being > RSIZE_MAX. + - s1max being zero. + - n >= s1max and s1max <= strnlen_s( s2, s1max ) (not enough space in s1). + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max is greater + zero and <= RSIZE_MAX, s1[0] will be set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t strncpy_s( char * _PDCLIB_restrict s1, rsize_t s1max, const char * _PDCLIB_restrict s2, rsize_t n ); + +/* Append the contents of the character array s2 (including terminating '\0') to + the character array s1 (first character of s2 overwriting the '\0' of s1). + Elements following the terminating null character (if any) take unspecified + values. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max being > RSIZE_MAX. + - s1max being zero. + - not enough space in s1 for both s1 and the characters copied from s2. + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max is greater + zero and <= RSIZE_MAX, s1[0] will be set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t strcat_s( char * _PDCLIB_restrict s1, rsize_t s1max, const char * _PDCLIB_restrict s2 ); + +/* Append a maximum of n characters from the character array s2 to the + character array s1 (first character of s2 overwriting the '\0' of s1). A + terminating '\0' is ALWAYS appended, even if the full n characters have + already been written. + Elements following the terminating null character (if any) take unspecified + values. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s1 or s2 being NULL. + - s1max or n being > RSIZE_MAX. + - s1max being zero. + - not enough space in s1 for both s1 and the characters copied from s2. + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max is greater + zero and <= RSIZE_MAX, s1[0] will be set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t strncat_s( char * _PDCLIB_restrict s1, rsize_t s1max, const char * _PDCLIB_restrict s2, rsize_t n ); + +/* In a series of subsequent calls, parse a C string into tokens. + On the first call to strtok(), the first argument is a pointer to the to-be- + parsed C string of size *s1max. On subsequent calls, the first argument is + NULL unless you want to start parsing a new string. s2 holds an array of + separator characters which can differ from call to call. Leading separators + are skipped, the first trailing separator overwritten with '\0'. + Returns a pointer to the next token. + The following conditions will be considered runtime constraint violations: + - s1max, s2, or ptr being NULL. + - s1max or n being > RSIZE_MAX. + - s1max being zero. + - not enough space in s1 for both s1 and the characters copied from s2. + - copying between overlapping objects. + In case of a constraint violation, if s1 is not NULL and s1max is greater + zero and <= RSIZE_MAX, s1[0] will be set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC char * strtok_s( char * _PDCLIB_restrict s1, rsize_t * _PDCLIB_restrict s1max, const char * _PDCLIB_restrict s2, char ** _PDCLIB_restrict ptr ); + +/* Write the character c (interpreted as unsigned char) to the first n + characters of the memory area pointed to by s of size smax. + Returns zero if successful, non-zero otherwise. + The following conditions will be considered runtime constraint violations: + - s being NULL. + - smax or n being > RSIZE_MAX. + - n being > smax. + In case of a constraint violation, if s is not NULL and smax is <= RSIZE_MAX + the value of c (interpreted as unsigned char) is written to the first smax + characters of s. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t memset_s( void * s, rsize_t smax, int c, rsize_t n ); + +/* Map an error number to a (locale-specific) error message string. Error + numbers are typically errno values, but any number is mapped to a message. + TODO: PDCLib does not yet support locales. + If the length of the mapped string is < maxsize, the string is copied to s. + Otherwise, if maxsize is greater than zero, as much of the string as does + fit is copied, and s[maxsize-1] set to '\0'. If maxsize is greater than 3, + the partial string is made to end in "...". + Returns zero if the string was copied successfully in full, non-zero + otherwise. + The following conditions will be considered runtime constraint violations: + - s being NULL. + - maxsize being zero or > RSIZE_MAX. + In case of a constraint violation, s is not modified. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t strerror_s( char * s, rsize_t maxsize, errno_t errnum ); + +/* Map an error number to a (locale-specific) error message string, the same + way as strerror_s() would do. Error numbers are typically errno values, + but any number is mapped to a message. + TODO: PDCLib does not yet support locales. + Returns the length of the mapped string. +*/ +_PDCLIB_PUBLIC size_t strerrorlen_s( errno_t errnum ); + +/* Returns the length of the string s (excluding terminating '\0'). + If there is no null character in the first maxsize characters of s, + rerturns maxsize. If s is NULL, returns zero. + At most the first maxsize characters of s shall be accessed by the + function. +*/ +_PDCLIB_PUBLIC size_t strnlen_s( const char * s, size_t maxsize ); + +#endif + +#ifdef __cplusplus +} +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_STRING_H +#include _PDCLIB_EXTEND_STRING_H +#endif + +#endif diff --git a/libc/include/threads.h b/libc/include/threads.h new file mode 100644 index 0000000..7de037d --- /dev/null +++ b/libc/include/threads.h @@ -0,0 +1,260 @@ +/* Threads + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +/* This header does not include any platform-specific information, but as + a whole is optional (depending on whether threads are supported or not, + ref. __STDC_NO_THREADS__), which is why it is located in the example + platform instead of the general include directory. +*/ + +#ifndef _PDCLIB_THREADS_H +#define _PDCLIB_THREADS_H _PDCLIB_THREADS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if __STDC_NO_THREADS__ == 1 +#error __STDC_NO_THREADS__ defined but included. Something is wrong about your setup. +#endif + +#if __STDC_VERSION__ >= 201112L +/* The rest of can work with a pre-C11 compiler just fine. */ +#define thread_local _Thread_local +#endif + +/* Initializing value for an object of type once_flag */ +#define ONCE_FLAG_INIT _PDCLIB_ONCE_FLAG_INIT + +/* Maximum number of times destructors are called on thread termination */ +#define TSS_DTOR_ITERATIONS _PDCLIB_TSS_DTOR_ITERATIONS + +/* Condition variable */ +typedef _PDCLIB_cnd_t cnd_t; + +/* Thread */ +typedef _PDCLIB_thrd_t thrd_t; + +/* Thread-specific storage */ +typedef _PDCLIB_tss_t tss_t; + +/* Mutex */ +typedef _PDCLIB_mtx_t mtx_t; + +/* TSS destructor */ +typedef void ( *tss_dtor_t )( void * ); + +/* Thread start function */ +typedef int ( *thrd_start_t )( void * ); + +/* Flag for use with call_once() */ +typedef _PDCLIB_once_flag once_flag; + +/* TODO: Documentation. */ +enum +{ + mtx_plain, + mtx_recursive, + mtx_timed +}; + +/* TODO: Documentation. */ +enum +{ + thrd_timedout, + thrd_success, + thrd_busy, + thrd_error, + thrd_nomem +}; + +/* Initialization functions */ + +/* Ensure that func is called only the first time call_once() is called + for a given flag. +*/ +_PDCLIB_PUBLIC void call_once( once_flag * flag, void ( *func )( void ) ); + +/* Condition variable functions */ + +/* Unblock threads waiting on given condition. + Returns thrd_success if successful, thrd_error if the request could not + be honored. +*/ +_PDCLIB_PUBLIC int cnd_broadcast( cnd_t * cond ); + +/* Destroy condition variable. + No threads may be waiting on a condition when it is destroyed. +*/ +_PDCLIB_PUBLIC void cnd_destroy( cnd_t * cond ); + +/* Initialize condition variable. + Returns thrd_success if successful, thrd_nomem if out of memory, and + thrd_error if the request could not be honored. + Initializes the variable in a way that a thread calling cnd_wait() on it + would block. +*/ +_PDCLIB_PUBLIC int cnd_init( cnd_t * cond ); + +/* Unblock one thread waiting on the condition variable. + Returns thrd_success if successful, thrd_error if the request could not + be honored. +*/ +_PDCLIB_PUBLIC int cnd_signal( cnd_t * cond ); + +/* TODO: Documentation. + Returns thrd_success if successful, thrd_timedout if the specified time + is reached without acquiring the resource, or thrd_error if the request + could not be honored. +*/ +_PDCLIB_PUBLIC int cnd_timedwait( cnd_t * _PDCLIB_restrict cond, mtx_t * _PDCLIB_restrict mtx, const struct timespec * _PDCLIB_restrict ts ); + +/* TODO: Documentation. + Returns thrd_success if successful, thrd_error if the request could not + be honored. +*/ +int cnd_wait( cnd_t * cond, mtx_t * mtx ); + +/* Mutex functions */ + +/* Destroy mutex variable. + No threads may be waiting on a mutex when it is destroyed. +*/ +_PDCLIB_PUBLIC void mtx_destroy( mtx_t * mtx ); + +/* Initialize mutex variable. + Returns thrd_success if successful, thrd_error if the request could not + be honored. + Type must have one of the following values: + mtx_plain -- non-recursive mutex + mtx_timed -- non-recursive mutex supporting timeout + mtx_plain | mtx_recursive -- recursive mutex + mtx_timed | mtx_recursive -- recursive mutex supporting timeout +*/ +_PDCLIB_PUBLIC int mtx_init( mtx_t * mtx, int type ); + +/* Try to lock the given mutex (blocking). + Returns thrd_success if successful, thrd_error if the request could not + be honored. + If the given mutex is non-recursive, it must not be already locked by + the calling thread. +*/ +_PDCLIB_PUBLIC int mtx_lock( mtx_t * mtx ); + +/* TODO: Documentation. + Returns thrd_success if successful, thrd_timedout if the specified time + is reached without acquiring the resource, or thrd_error if the request + could not be honored. +*/ +_PDCLIB_PUBLIC int mtx_timedlock( mtx_t * _PDCLIB_restrict mtx, const struct timespec * _PDCLIB_restrict ts ); + +/* Try to lock the given mutex (non-blocking). + Returns thrd_success if successful, thrd_busy if the resource is already + locked, or thrd_error if the request could not be honored. +*/ +_PDCLIB_PUBLIC int mtx_trylock( mtx_t * mtx ); + +/* Unlock the given mutex. + Returns thrd_success if successful, thrd_error if the request could not + be honored. + The given mutex must be locked by the calling thread. +*/ +_PDCLIB_PUBLIC int mtx_unlock( mtx_t * mtx ); + +/* Thread functions */ + +/* Create a new thread. + Returns thrd_success if successful, thrd_nomem if out of memory, and + thrd_error if the request could not be honored. + Create a new thread executing func( arg ), and sets thr to identify + the created thread. (Identifiers may be reused afer a thread exited + and was either detached or joined.) +*/ +_PDCLIB_PUBLIC int thrd_create( thrd_t * thr, thrd_start_t func, void * arg ); + +/* Identify the calling thread. + Returns the identifier of the calling thread. +*/ +_PDCLIB_PUBLIC thrd_t thrd_current( void ); + +/* Notify the OS to destroy all resources of a given thread once it + terminates. + Returns thrd_success if successful, thrd_error if the request could not + be honored. + The given thread must not been previously detached or joined. +*/ +_PDCLIB_PUBLIC int thrd_detach( thrd_t thr ); + +/* Compare two thread identifiers for equality. + Returns nonzero if both parameters identify the same thread, zero + otherwise. +*/ +_PDCLIB_PUBLIC int thrd_equal( thrd_t thr0, thrd_t thr1 ); + +/* Terminate calling thread, set result code to res. + When the last thread of a program terminates the program shall terminate + normally as if by exit( EXIT_SUCCESS ). + FIXME: The result code setting is NOT implemented correctly at this point. + The value is indeterminate. +*/ +_PDCLIB_PUBLIC _PDCLIB_Noreturn void thrd_exit( int res ) _PDCLIB_NORETURN; + +/* Join the given thread with the calling thread. + Returns thrd_success if successful, thrd_error if the request could not + be honored. + Function blocks until the given thread terminates. If res is not NULL, + the given thread's result code will be stored at that address. +*/ +_PDCLIB_PUBLIC int thrd_join( thrd_t thr, int * res ); + +/* Suspend the calling thread for the given duration or until a signal not + being ignored is received. + Returns zero if the requested time has elapsed, -1 if interrupted by a + signal, negative if the request failed. + If remaining is not NULL, and the sleeping thread received a signal that + is not being ignored, the remaining time (duration minus actually elapsed + time) shall be stored at that address. +*/ +_PDCLIB_PUBLIC int thrd_sleep( const struct timespec * duration, struct timespec * remaining ); + +/* Permit other threads to run. */ +_PDCLIB_PUBLIC void thrd_yield( void ); + +/* Thread-specific storage functions */ + +/* Initialize thread-specific storage, with optional destructor + Returns thrd_success if successful, thrd_error otherwise (in this case + key is set to an undefined value). +*/ +_PDCLIB_PUBLIC int tss_create( tss_t * key, tss_dtor_t dtor ); + +/* Release all resources of a given thread-specific storage. */ +_PDCLIB_PUBLIC void tss_delete( tss_t key ); + +/* Returns the value for the current thread associated with the given key. +*/ +_PDCLIB_PUBLIC void * tss_get( tss_t key ); + +/* Sets the value associated with the given key for the current thread. + Returns thrd_success if successful, thrd_error if the request could not + be honored. +*/ +_PDCLIB_PUBLIC int tss_set( tss_t key, void * val ); + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_THREADS_H +#include _PDCLIB_EXTEND_THREADS_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/time.h b/libc/include/time.h new file mode 100644 index 0000000..17ae34f --- /dev/null +++ b/libc/include/time.h @@ -0,0 +1,191 @@ +/* Date and time + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_TIME_H +#define _PDCLIB_TIME_H _PDCLIB_TIMEH + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_lib_ext1.h" +#include "pdclib/_PDCLIB_internal.h" + +#ifndef _PDCLIB_SIZE_T_DEFINED +#define _PDCLIB_SIZE_T_DEFINED _PDCLIB_SIZE_T_DEFINED +typedef _PDCLIB_size_t size_t; +#endif + +#ifndef _PDCLIB_NULL_DEFINED +#define _PDCLIB_NULL_DEFINED _PDCLIB_NULL_DEFINED +#define NULL _PDCLIB_NULL +#endif + +/* See comments in _PDCLIB_config.h on the semantics of time_t and clock_t. */ + +typedef _PDCLIB_time_t time_t; +typedef _PDCLIB_clock_t clock_t; + +#define CLOCKS_PER_SEC _PDCLIB_CLOCKS_PER_SEC +#define TIME_UTC _PDCLIB_TIME_UTC + +/* Implementor's note: If you change this structure, and are using Pthread + threading support, check auxiliary/pthread/pthread_readout.c for its + twin. It is imperative that Pthread and PDCLib use identical layouts for + struct timespec, as they are implicitly cast from one to the other. This + cannot be checked for in this header (as we may not include host system + headers here), so the assert()s are in pthread_readout.c (which, in turn, + cannot include *this* header, which is why this admonishment to keep the + definitions in sync exists...). +*/ +struct timespec +{ + time_t tv_sec; + long tv_nsec; +}; + +struct tm +{ + int tm_sec; /* 0-60 */ + int tm_min; /* 0-59 */ + int tm_hour; /* 0-23 */ + int tm_mday; /* 1-31 */ + int tm_mon; /* 0-11 */ + int tm_year; /* years since 1900 */ + int tm_wday; /* 0-6 */ + int tm_yday; /* 0-365 */ + int tm_isdst; /* >0 DST, 0 no DST, <0 information unavailable */ +}; + +/* Returns the number of "clocks" in processor time since the invocation + of the program. Divide by CLOCKS_PER_SEC to get the value in seconds. + Returns -1 if the value cannot be represented in the return type or is + not available. +*/ +_PDCLIB_PUBLIC clock_t clock( void ); + +/* Returns the difference between two calendar times in seconds. */ +_PDCLIB_PUBLIC double difftime( time_t time1, time_t time0 ); + +/* Normalizes the values in the broken-down time pointed to by timeptr. + Returns the calender time specified by the broken-down time. +*/ +_PDCLIB_PUBLIC time_t mktime( struct tm * timeptr ); + +/* Returns the current calender time. If timer is not a NULL pointer, stores + the current calender time at that address as well. +*/ +_PDCLIB_PUBLIC time_t time( time_t * timer ); + +/* Sets the interval pointed to by ts to the current calender time, based + on the specified base. + Returns base, if successful, otherwise zero. +*/ +_PDCLIB_PUBLIC int timespec_get( struct timespec * ts, int base ); + +/* Converts the broken-down time pointed to by timeptr into a string in the + form "Sun Sep 16 01:03:52 1973\n\0". +*/ +_PDCLIB_PUBLIC char * asctime( const struct tm * timeptr ); + +/* Equivalent to asctime( localtime( timer ) ). */ +_PDCLIB_PUBLIC char * ctime( const time_t * timer ); + +/* Converts the calender time pointed to by timer into a broken-down time + expressed as UTC. + Returns a pointer to the broken-down time, or a NULL pointer if it + cannot be represented. +*/ +_PDCLIB_PUBLIC struct tm * gmtime( const time_t * timer ); + +/* Converts the calender time pointed to by timer into a broken-down time + expressed as local time. + Returns a pointer to the broken-down time, or a NULL pointer if if + cannot be represented. +*/ +_PDCLIB_PUBLIC struct tm * localtime( const time_t * timer ); + +/* Writes the broken-down time pointed to by timeptr into the character + array pointed to by s. The string pointed to by format controls the + exact output. No more than maxsize charactrs will be written. + Returns the number of characters written (excluding the terminating + null character), or zero on failure. +*/ +_PDCLIB_PUBLIC size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB_restrict format, const struct tm * _PDCLIB_restrict timeptr ); + +/* Annex K -- Bounds-checking interfaces */ + +#if ( __STDC_WANT_LIB_EXT1__ + 0 ) != 0 + +#ifndef _PDCLIB_ERRNO_T_DEFINED +#define _PDCLIB_ERRNO_T_DEFINED _PDCLIB_ERRNO_T_DEFINED +typedef int errno_t; +#endif + +#ifndef _PDCLIB_RSIZE_T_DEFINED +#define _PDCLIB_RSIZE_T_DEFINED _PDCLIB_RSIZE_T_DEFINED +typedef _PDCLIB_size_t rsize_t; +#endif + +/* Converts the broken-down time pointed to by timeptr into a string in the + form "Sun Sep 16 01:03:52 1973\n\0", which is stored in buffer s of maxsize. + Returns zero if the time was successfully converted and stored, non-zero + otherwise. + The following conditions will be considered runtime constraint violations: + - s or timeptr being NULL. + - maxsize being < 26 or > RSIZE_MAX. + - the broken-down time pointed to by timeptr not being normalized. + - the year represented by the broken-down time pointed to by timeptr + being < 0 or > 9999. + In case of a constraint violation, the time will not be converted. If + s is not NULL and maxsize is neither zero nor > RSIZE_MAX, s[0] will be + set to '\0'. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC errno_t asctime_s( char * s, rsize_t maxsize, const struct tm * timeptr ); + +/* Equivalent to asctime_s( s, maxsize, localtime( timer ) ). */ +_PDCLIB_PUBLIC errno_t ctime_s( char * s, rsize_t maxsize, const time_t * timer ); + +/* Converts the calender time pointed to by timer into a broken-down time + expressed as UTC, which gets stored in the result struct. + Returns a pointer to the broken-down time, or a NULL pointer if if + cannot be represented or stored. + The following conditions will be considered runtime constraint violations: + - timer or result being NULL. + In case of a constraint violation, the time will not be converted. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC struct tm * gmtime_s( const time_t * _PDCLIB_restrict timer, struct tm * _PDCLIB_restrict result ); + +/* Converts the calender time pointed to by timer into a broken-down time + expressed as local time, which gets stored in the result struct. + Returns a pointer to the broken-down time, or a NULL pointer if if + cannot be represented or stored. + The following conditions will be considered runtime constraint violations: + - timer or result being NULL. + In case of a constraint violation, the time will not be converted. + The currently active constraint violation handler function will be called + (see set_constraint_handler_s()). +*/ +_PDCLIB_PUBLIC struct tm * localtime_s( const time_t * _PDCLIB_restrict timer, struct tm * _PDCLIB_restrict result ); + +#endif + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_TIME_H +#include _PDCLIB_EXTEND_TIME_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/include/wctype.h b/libc/include/wctype.h new file mode 100644 index 0000000..3776b7a --- /dev/null +++ b/libc/include/wctype.h @@ -0,0 +1,152 @@ +/* Wide character classification and mapping utilities + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#ifndef _PDCLIB_WCTYPE_H +#define _PDCLIB_WCTYPE_H _PDCLIB_WCTYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pdclib/_PDCLIB_internal.h" + +typedef _PDCLIB_wint_t wint_t; +typedef int wctrans_t; +typedef int wctype_t; + +#ifndef _PDCLIB_WEOF_DEFINED +#define _PDCLIB_WEOF_DEFINED _PDCLIB_WEOF_DEFINED +#define WEOF (wint_t)-1 +#endif + +/* Wide character classification functions */ + +/* Returns iswalpha( wc ) || iswdigit( wc ) */ +_PDCLIB_PUBLIC int iswalnum( wint_t wc ); + +/* Returns true for wide characters for which either isupper( wc ) or + islower( wc ) is true, as well as a set of locale-specific wide + characters which are neither control characters, digits, punctuation, + or whitespace. +*/ +_PDCLIB_PUBLIC int iswalpha( wint_t wc ); + +/* Returns true if the character iswspace() and used for separating words + within a line of text. In the "C" locale, only L' ' and L'\t' are + considered blanks. +*/ +_PDCLIB_PUBLIC int iswblank( wint_t wc ); + +/* Returns true if the wide character is a control character. */ +_PDCLIB_PUBLIC int iswcntrl( wint_t wc ); + +/* Returns true if the wide character is a decimal digit. Locale- + independent. */ +_PDCLIB_PUBLIC int iswdigit( wint_t wc ); + +/* Returns iswprint( wc ) && ! iswspace( wc ). + NOTE: This definition differs from that of isgraph() in , + which considers only ' ', not all isspace() characters. +*/ +_PDCLIB_PUBLIC int iswgraph( wint_t wc ); + +/* Returns true for lowerspace wide characters, as well as a set of + locale-specific wide characters which are neither control charcters, + digits, punctuation, or whitespace. +*/ +_PDCLIB_PUBLIC int iswlower( wint_t wc ); + +/* Returns true for every printing wide character. */ +_PDCLIB_PUBLIC int iswprint( wint_t wc ); + +/* Returns true for a locale-specific set of punctuation characters that + are neither whitespace nor alphanumeric. +*/ +_PDCLIB_PUBLIC int iswpunct( wint_t wc ); + +/* Returns true for a locale-specific set of whitespace characters that + are neither alphanumeric, graphic, or punctuation. +*/ +_PDCLIB_PUBLIC int iswspace( wint_t wc ); + +/* Returns true for upperspace wide characters, as well as a set of + locale-specific wide characters which are neither control charcters, + digits, punctuation, or whitespace. +*/ +_PDCLIB_PUBLIC int iswupper( wint_t wc ); + +/* Returns true if the wide character is a hexadecimal digit. Locale- + independent. */ +_PDCLIB_PUBLIC int iswxdigit( wint_t wc ); + +/* Extensible wide character classification functions */ + +/* Returns true if the wide character wc has the property described by + desc (which was retrieved by a previous call to wctype() without + changing the LC_CTYPE locale setting between the two calls). +*/ +_PDCLIB_PUBLIC int iswctype( wint_t wc, wctype_t desc ); + +/* Returns a description object for a named character property, to be + used as parameter to the iswctype() function. Supported property + names are: + "alnum" -- alphanumeric, as per iswalnum() + "alpha" -- alphabetic, as per iswalpha() + "blank" -- blank, as per iswblank() + "cntrl" -- control, as per iswcntrl() + "digit" -- decimal digit, as per iswdigit() + "graph" -- graphic, as per iswgraph() + "lower" -- lowercase, as per iswlower() + "print" -- printing, as per iswprint() + "punct" -- punctuation, as per iswprint() + "space" -- whitespace, as per iswspace() + "upper" -- uppercase, as per iswupper() + "xdigit" -- hexadecimal digit, as per iswxdigit() + For unsupported properties, the function returns zero. +*/ +_PDCLIB_PUBLIC wctype_t wctype( const char * property ); + +/* Wide character case mapping utilities */ + +/* Converts an uppercase letter to a corresponding lowercase letter. Input for + which no corresponding lowercase letter exists remains unchanged. +*/ +_PDCLIB_PUBLIC wint_t towlower( wint_t wc ); + +/* Converts a lowercase letter to a corresponding uppercase letter. Input for + which no corresponding uppercase letter exists remains unchanged. +*/ +_PDCLIB_PUBLIC wint_t towupper( wint_t wc ); + +/* Extensible wide character case mapping utilities */ + +/* Converts the wide character wc according to the transition described + by desc (which was retrieved by a previous call to wctrans() without + changing the LC_CTYPE locale setting between the two calls). +*/ +_PDCLIB_PUBLIC wint_t towctrans( wint_t wc, wctrans_t desc ); + +/* Returns a description object for a named character transformation, to + be used as parameter to the towctrans() function. Supported transformation + properties are: + "tolower" -- lowercase mapping, as per towlower() + "toupper" -- uppercase mapping, as per towupper() + For unsupported properties, the function returns zero. +*/ +_PDCLIB_PUBLIC wctrans_t wctrans( const char * property ); + +/* Extension hook for downstream projects that want to have non-standard + extensions to standard headers. +*/ +#ifdef _PDCLIB_EXTEND_WCTYPE_H +#include _PDCLIB_EXTEND_WCTYPE_H +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libc/memcmp.c b/libc/memcmp.c new file mode 100644 index 0000000..2c9fdfe --- /dev/null +++ b/libc/memcmp.c @@ -0,0 +1,46 @@ +/* memcmp( const void *, const void *, size_t ) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#include + +#ifndef REGTEST + +int memcmp( const void * s1, const void * s2, size_t n ) +{ + const unsigned char * p1 = ( const unsigned char * ) s1; + const unsigned char * p2 = ( const unsigned char * ) s2; + + while ( n-- ) + { + if ( *p1 != *p2 ) + { + return *p1 - *p2; + } + + ++p1; + ++p2; + } + + return 0; +} + +#endif + +#ifdef TEST + +#include "_PDCLIB_test.h" + +int main( void ) +{ + const char xxxxx[] = "xxxxx"; + TESTCASE( memcmp( abcde, abcdx, 5 ) < 0 ); + TESTCASE( memcmp( abcde, abcdx, 4 ) == 0 ); + TESTCASE( memcmp( abcde, xxxxx, 0 ) == 0 ); + TESTCASE( memcmp( xxxxx, abcde, 1 ) > 0 ); + return 0; +} + +#endif diff --git a/libc/strncat.c b/libc/strncat.c new file mode 100644 index 0000000..b39145a --- /dev/null +++ b/libc/strncat.c @@ -0,0 +1,64 @@ +/* strncat( char *, const char *, size_t ) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#include + +#ifndef REGTEST + +char * strncat( char * s1, const char * s2, size_t n ) +{ + char * rc = s1; + + while ( *s1 ) + { + ++s1; + } + + while ( n && ( *s1++ = *s2++ ) ) + { + --n; + } + + if ( n == 0 ) + { + *s1 = '\0'; + } + + return rc; +} + +#endif + +#ifdef TEST + +#include "_PDCLIB_test.h" + +int main( void ) +{ + char s[] = "xx\0xxxxxx"; + TESTCASE( strncat( s, abcde, 10 ) == s ); + TESTCASE( s[2] == 'a' ); + TESTCASE( s[6] == 'e' ); + TESTCASE( s[7] == '\0' ); + TESTCASE( s[8] == 'x' ); + s[0] = '\0'; + TESTCASE( strncat( s, abcdx, 10 ) == s ); + TESTCASE( s[4] == 'x' ); + TESTCASE( s[5] == '\0' ); + TESTCASE( strncat( s, "\0", 10 ) == s ); + TESTCASE( s[5] == '\0' ); + TESTCASE( s[6] == 'e' ); + TESTCASE( strncat( s, abcde, 0 ) == s ); + TESTCASE( s[5] == '\0' ); + TESTCASE( s[6] == 'e' ); + TESTCASE( strncat( s, abcde, 3 ) == s ); + TESTCASE( s[5] == 'a' ); + TESTCASE( s[7] == 'c' ); + TESTCASE( s[8] == '\0' ); + return TEST_RESULTS; +} + +#endif diff --git a/libc/strncmp.c b/libc/strncmp.c new file mode 100644 index 0000000..6b35763 --- /dev/null +++ b/libc/strncmp.c @@ -0,0 +1,55 @@ +/* strncmp( const char *, const char *, size_t ) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#include + +#ifndef REGTEST + +int strncmp( const char * s1, const char * s2, size_t n ) +{ + while ( n && *s1 && ( *s1 == *s2 ) ) + { + ++s1; + ++s2; + --n; + } + + if ( n == 0 ) + { + return 0; + } + else + { + return ( *( unsigned char * )s1 - * ( unsigned char * )s2 ); + } +} + +#endif + +#ifdef TEST + +#include "_PDCLIB_test.h" + +int main( void ) +{ + char cmpabcde[] = "abcde\0f"; + char cmpabcd_[] = "abcde\xfc"; + char empty[] = ""; + char x[] = "x"; + TESTCASE( strncmp( abcde, cmpabcde, 5 ) == 0 ); + TESTCASE( strncmp( abcde, cmpabcde, 10 ) == 0 ); + TESTCASE( strncmp( abcde, abcdx, 5 ) < 0 ); + TESTCASE( strncmp( abcdx, abcde, 5 ) > 0 ); + TESTCASE( strncmp( empty, abcde, 5 ) < 0 ); + TESTCASE( strncmp( abcde, empty, 5 ) > 0 ); + TESTCASE( strncmp( abcde, abcdx, 4 ) == 0 ); + TESTCASE( strncmp( abcde, x, 0 ) == 0 ); + TESTCASE( strncmp( abcde, x, 1 ) < 0 ); + TESTCASE( strncmp( abcde, cmpabcd_, 10 ) < 0 ); + return TEST_RESULTS; +} + +#endif diff --git a/libc/strncpy.c b/libc/strncpy.c new file mode 100644 index 0000000..2a89fb2 --- /dev/null +++ b/libc/strncpy.c @@ -0,0 +1,59 @@ +/* strncpy( char *, const char *, size_t ) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#include + +#ifndef REGTEST + +char * strncpy( char * s1, const char * s2, size_t n ) +{ + char * rc = s1; + + while ( n && ( *s1++ = *s2++ ) ) + { + /* Cannot do "n--" in the conditional as size_t is unsigned and we have + to check it again for >0 in the next loop below, so we must not risk + underflow. + */ + --n; + } + + /* Checking against 1 as we missed the last --n in the loop above. */ + while ( n-- > 1 ) + { + *s1++ = '\0'; + } + + return rc; +} + +#endif + +#ifdef TEST + +#include "_PDCLIB_test.h" + +int main( void ) +{ + char s[] = "xxxxxxx"; + TESTCASE( strncpy( s, "", 1 ) == s ); + TESTCASE( s[0] == '\0' ); + TESTCASE( s[1] == 'x' ); + TESTCASE( strncpy( s, abcde, 6 ) == s ); + TESTCASE( s[0] == 'a' ); + TESTCASE( s[4] == 'e' ); + TESTCASE( s[5] == '\0' ); + TESTCASE( s[6] == 'x' ); + TESTCASE( strncpy( s, abcde, 7 ) == s ); + TESTCASE( s[6] == '\0' ); + TESTCASE( strncpy( s, "xxxx", 3 ) == s ); + TESTCASE( s[0] == 'x' ); + TESTCASE( s[2] == 'x' ); + TESTCASE( s[3] == 'd' ); + return TEST_RESULTS; +} + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..0e16eac --- /dev/null +++ b/main.c @@ -0,0 +1,67 @@ + +#include "main.h" + +#include +#include "runtime/panic_assert.h" +#include "runtime/stdio.h" +#include "graphics/graphics.h" +#include "graphics/unifont.h" +#include "memory/memory.h" + + +FASTCALL_ABI EFI_STATUS efiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { + SystemTable->ConOut->EnableCursor(SystemTable->ConOut, TRUE); + + efiImageHandle = ImageHandle; + efiSystemTable = SystemTable; + efiBootServices = SystemTable->BootServices; + efiStdin = SystemTable->ConIn; + efiStdout = SystemTable->ConOut; + efiStderr = SystemTable->StdErr; + + // disable the watchdog timer so that the application does not reset after 5mins + efiBootServices->SetWatchdogTimer(0, 0, 0, NULL); + + io_WriteConsole(PROJECT_NAME "\r\n\r\nAWAITING FOR USER INPUT"); + + // wait for a user keypress + UINTN index; + SystemTable->BootServices->WaitForEvent(1, &SystemTable->ConIn->WaitForKey, &index); + EFI_INPUT_KEY key; + SystemTable->ConIn->ReadKeyStroke(SystemTable->ConIn, &key); + io_WriteConsole("...Key Pressed\r\n"); + io_Printf("Pressed key: %d, %d\r\n", (int)key.ScanCode, (int)key.UnicodeChar); + + + graphics_Init(); + io_PauseForKeystroke(); + + graphics_ClearBuffer(&HelosGraphics_Color_Black); + graphics_FillPixel(0, 0, 20, 20, &HelosGraphics_Color_Black); + graphics_FillPixel(20, 0, 40, 20, &HelosGraphics_Color_White); + graphics_FillPixel(40, 0, 60, 20, &HelosGraphics_Color_Red); + graphics_FillPixel(60, 0, 80, 20, &HelosGraphics_Color_Green); + graphics_FillPixel(80, 0, 100, 20, &HelosGraphics_Color_Blue); + graphics_FillPixel(100, 0, 120, 20, &HelosGraphics_Color_Cyan); + graphics_FillPixel(120, 0, 140, 20, &HelosGraphics_Color_Magenta); + graphics_FillPixel(140, 0, 160, 20, &HelosGraphics_Color_Yellow); + + uint32_t helloString[] = {0x89C6, 0x7A97, 0x7CFB, 0x7EDF, 0x4E09, ' ', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'a', 'n', 'd', ',', ' ', 'n', 'i', 'c', 'e', ' ', 't', 'o', ' ', 'm', 'e', 'e', 't', ' ', 'y', 'o', 'u', '!'}; + unifont_DrawString(0, 20, &HelosGraphics_Color_White, helloString, sizeof(helloString) / sizeof(uint32_t)); + graphics_CursorX = 0; + graphics_CursorY = 52; + + runtime_InitPaging(); + + + return EFI_SUCCESS; +} + +EFI_HANDLE efiImageHandle; +EFI_SYSTEM_TABLE * efiSystemTable; +EFI_BOOT_SERVICES *efiBootServices; + +SIMPLE_TEXT_OUTPUT_INTERFACE *efiStdout, *efiStderr; +SIMPLE_INPUT_INTERFACE * efiStdin; + +char Buffer[HELOS_BUFFER_SIZE]; diff --git a/main.h b/main.h new file mode 100644 index 0000000..a8a4f90 --- /dev/null +++ b/main.h @@ -0,0 +1,48 @@ + +#ifdef __cplusplus +extern "C" { +#include +} +#else +#include +#endif + +#define PROJECT_NAME "Helos1" +#define PROJECT_NAME_LONG L"Helos1" + +#define FASTCALL_ABI __attribute__((ms_abi)) // declares a function as Microsoft x64 ABI. Used in the EFI part of the kernel. +#define SYSV_ABI __attribute__((sysv_abi)) // declares a function as System V AMD64 ABI. Used in the Kernel space (other than EFI bootstrap code). + +#define ALIGNED(n) __attribute__((__aligned__(n))) +#define PACKED __attribute__((__packed__)) + +#if (defined __STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#define NORETURN _Noreturn +#else +#define NORETURN __attribute__((__noreturn__)) +#endif + +#define noreturn NORETURN + +// Don't (uint64_t) a function symbol directly, use this +#define LOCATE_SYMBOL(var, symbol) \ + asm volatile("leaq " #symbol "(%%rip), %0" \ + : "=r"(var)) + + +extern EFI_HANDLE efiImageHandle; +extern EFI_SYSTEM_TABLE * efiSystemTable; +extern EFI_BOOT_SERVICES *efiBootServices; + +extern SIMPLE_TEXT_OUTPUT_INTERFACE *efiStdout, *efiStderr; +extern SIMPLE_INPUT_INTERFACE * efiStdin; + +#define HELOS_BUFFER_SIZE 16384 +extern char Buffer[HELOS_BUFFER_SIZE]; // general-purpose buffer, user saved (volatile), not used in interrupt handlers + + +extern const char link_TextStart[], link_TextEnd[]; +extern const char link_DataStart[], link_DataEnd[]; +extern const char link_RodataStart[], link_RodataEnd[]; +extern const char link_BssStart[], link_BssEnd[]; +extern const char link_RelocStart[], link_RelocEnd[]; diff --git a/memory/Makefile b/memory/Makefile new file mode 100644 index 0000000..a829280 --- /dev/null +++ b/memory/Makefile @@ -0,0 +1,19 @@ + +SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +objects = $(patsubst %.c,%.o,$(wildcard *.c)) $(patsubst %.cpp,%.o,$(wildcard *.cpp)) +objects_fasm = $(patsubst %.S,%.o,$(wildcard *.S)) + +objects_test = $(patsubst %.c,%.o,$(wildcard test_*.c)) $(patsubst %.cpp,%.o,$(wildcard test_*.cpp)) +objects := $(filter-out $(objects_test),$(objects)) + + +all: $(objects) $(objects_fasm) + +$(objects_fasm): + $(FASM) $(patsubst %.o,%.S,$@) $@ + +clean: + -$(RM) *.o + + diff --git a/memory/liballoc_impl.c b/memory/liballoc_impl.c new file mode 100644 index 0000000..4b0f26b --- /dev/null +++ b/memory/liballoc_impl.c @@ -0,0 +1,41 @@ + +#include "memory.h" +#include "paging_internal.h" +#include "../runtime/stdio.h" +#include "../interrupt/interrupt.h" +#include + +#include "../extlib/liballoc/liballoc_1_1.h" + + +int liballoc_lock() { + if (interrupt_Enabled) + asm volatile("cli"); + return 0; +} + +int liballoc_unlock() { + if (interrupt_Enabled) + asm volatile("sti"); + return 0; +} + + +static uint64_t heapBreak = KERNEL_HEAP_VIRTUAL; + +void *liballoc_alloc(size_t pages) { + void *ret = (void *)heapBreak; + heapBreak += SYSTEM_PAGE_SIZE * pages; + + paging_map_PageAllocated((uint64_t)ret, pages, MAP_PROT_READ | MAP_PROT_WRITE); + + io_Printf("liballoc_alloc: allocated %u pages at HEAP+%llx (%llx)\n", pages, ret - KERNEL_HEAP_VIRTUAL, ret); + + return ret; +} + +int liballoc_free(void *ptr, size_t pages) { + paging_map_FreeAllocated((uint64_t)ptr, (uint64_t)ptr + SYSTEM_PAGE_SIZE * pages); + io_Printf("liballoc_free: freed %u pages at HEAP+%llx (%llx)\n", pages, ptr - KERNEL_HEAP_VIRTUAL, ptr); + return 0; +} diff --git a/memory/memory.c b/memory/memory.c new file mode 100644 index 0000000..2e62c2d --- /dev/null +++ b/memory/memory.c @@ -0,0 +1,40 @@ + +#include "../main.h" +#include "memory.h" +#include "../runtime/stdio.h" +#include "../extlib/liballoc/liballoc_1_1.h" + +#include + + +void *efiMallocTyped(size_t size, EFI_MEMORY_TYPE type) { + void *data; + efiBootServices->AllocatePool(type, size, &data); + memset(data, 0, size); + return data; +} + +void *efiMalloc(size_t size) { + return efiMallocTyped(size, EfiLoaderData); +} + +void efiFree(void *data) { + efiBootServices->FreePool(data); +} + +void *kMalloc(size_t size) { + void *mem = liballoc_kmalloc(size); + io_Printf("kMalloc: size=%llu, pos=0x%llx\n", size, mem); + return mem; +} + +void kFree(void *data) { + io_Printf("kFree: 0x%llx\n", data); + liballoc_kfree(data); +} + +void *kMemoryMap(void *desiredVirtual, int pageCount, int protectionFlags, int flags, int fd) { +} + +void kMemoryUnmap(void *pageStart, int pageCount) { +} diff --git a/memory/memory.h b/memory/memory.h new file mode 100644 index 0000000..87f7d78 --- /dev/null +++ b/memory/memory.h @@ -0,0 +1,100 @@ +#pragma once + +#include "../main.h" +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +#define KERNEL_CODE_VIRTUAL 0xFFFFFFFFC0000000ull // 2^64 - 1GiB +#define KERNEL_STACK_END_VIRTUAL (KERNEL_CODE_VIRTUAL) // kernel stack sits right below kernel code +#define KERNEL_STACK_INITIAL_SIZE SYSTEM_PAGE_2M_SIZE // currently must be 2 MiB +#define KERNEL_HEAP_VIRTUAL 0xFFFFFFFF00000000ull // 2^64 - 4GiB +#define KERNEL_FRAMEBUFFER_MAPPING 0xFFFFFFFEE0000000ull // 2^64 - 4GiB - 512MiB +#define KERNEL_MISC_MAPPING 0xFFFFFFFEC0000000ull // 2^64 - 5GiB + +#define KERNEL_IDT_MAPPING KERNEL_MISC_MAPPING +#define KERNEL_IDT_SIZE (256ull * 16) // fill the 256 interrupt vectors + +#define KERNEL_GDT_MAPPING (KERNEL_MISC_MAPPING + KERNEL_IDT_SIZE) +#define KERNEL_GDT_SIZE (16ull * 8) + +#define KERNEL_IDTR_MAPPING (KERNEL_MISC_MAPPING + KERNEL_IDT_SIZE + KERNEL_GDT_SIZE) +#define KERNEL_GDTR_MAPPING (KERNEL_MISC_MAPPING + KERNEL_IDT_SIZE + KERNEL_GDT_SIZE + 12) + +#define KERNEL_MISC_NEXT (KERNEL_MISC_MAPPING + KERNEL_IDT_SIZE + KERNEL_GDT_SIZE + 24) + +#define KERNEL_MISC_SIZE (KERNEL_IDT_SIZE + KERNEL_GDT_SIZE + 24) // add all the misc sizes + + +extern uint64_t paging_LoaderCodeAddress, paging_LoaderCodeSize; // physical address for loader code section +static inline uint64_t paging_MapFunction(void *func) { + return ((uint64_t)func - paging_LoaderCodeAddress) + KERNEL_CODE_VIRTUAL; +} + + +// efiMallocTyped allocates from EFI_BOOT_SERVICES.AllocatePool. +void *efiMallocTyped(size_t size, EFI_MEMORY_TYPE type); + +// efiMallocTyped allocates from EFI_BOOT_SERVICES.AllocatePool +// with a memory type of EfiLoaderData. +void *efiMalloc(size_t size); + +// efiFree frees data allocated from efiMalloc. +void efiFree(void *data); + + +// kMalloc allocates from system memory directly after paging has been set up +void *kMalloc(size_t size); + +// kFree frees data allocated from kMalloc. +void kFree(void *data); + + +extern EFI_MEMORY_DESCRIPTOR *efiMemoryMap; +extern UINTN efiMemoryMapSize; +extern UINTN efiMemoryMapKey; +extern UINTN efiDescriptorSize; +extern UINT32 efiDescriptorVertion; + +// runtime_InitPaging initializes paging and kMalloc/kFree allocator. +// This function calls ExitBootServices()!!! which is great +// Furthermore, it sets up a new stack, calls kMain() and does not return. +// +// If it fails, Panic() is called. +noreturn void runtime_InitPaging(); + +#define SYSTEM_PAGE_SIZE 4096ull // UEFI uses 4KiB pages by default +#define SYSTEM_PAGE_2M_SIZE 2097152ull // 2 MiB page size +#define SYSTEM_PAGE_1G_SIZE 1073741824ull // 1 GiB page size +#define MAX_SYSTEM_MEMORY_PAGES 16777216ull // 64 GiB + + +#define MAP_PROT_NONE 0 +#define MAP_PROT_EXEC 1 +#define MAP_PROT_WRITE 2 +#define MAP_PROT_READ 4 + +#define MAP_DATA 1 // Data, not initialized (kept as-is) +#define MAP_INITIALIZED_DATA 2 // Data, zeroed +#define MAP_FILE 3 // Memory-mapped file IO + +// kMemoryPageSize returns the size, in bytes, of the physical memory page. +inline static size_t kMemoryPageSize() { + return SYSTEM_PAGE_SIZE; +} + +// kMemoryMap maps new physical pages into the kernel virtual memory space. +// +// The memory is not cleared. +void *kMemoryMap(void *desiredVirtual, int pageCount, int protectionFlags, int dataType, int fd); + +// kMemoryUnmap unmaps previously mapped physical memory of the kernel space. +void kMemoryUnmap(void *pageStart, int pageCount); + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/memory/memory.hpp b/memory/memory.hpp new file mode 100644 index 0000000..f377e5a --- /dev/null +++ b/memory/memory.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include + +#include "memory.h" + + +namespace helos { + + +class bad_alloc: public std::exception { +public: + bad_alloc() noexcept {} + bad_alloc(const bad_alloc &other) noexcept {} + bad_alloc &operator=(const bad_alloc &other) noexcept { return *this; } + + virtual const char *what() const noexcept override { return "helos::bad_alloc"; } +}; + + +// kAllocator is a class wrapper for kMalloc/Free satisfying the named requirement Allocator. +template +class kAllocator { +public: + typedef Type value_type; + + kAllocator() = default; + template + constexpr kAllocator(const kAllocator &) {} + + Type *allocate(std::size_t n) { + return kMalloc(n * sizeof(Type)); + } + + void deallocate(Type *p, std::size_t n) { + kFree(p); + } +}; + +template +bool operator==(const kAllocator &, const kAllocator &) { + return true; +} +template +bool operator!=(const kAllocator &, const kAllocator &) { + return false; +} + +} // namespace helos + + +// globally overload the new and delete operators +// so keep this header at the top of every source file +// +// operators new and delete only call kMalloc/kFree, so C++ code +// must stay after paging setup +void *operator new(std::size_t size); +void operator delete(void *ptr) noexcept; diff --git a/memory/memory_cpp.cpp b/memory/memory_cpp.cpp new file mode 100644 index 0000000..b35f4fa --- /dev/null +++ b/memory/memory_cpp.cpp @@ -0,0 +1,13 @@ +#include "memory.hpp" + +void *operator new(std::size_t size) { + void *data = kMalloc(size); + /*if (!data) { + throw helos::bad_alloc(); + }*/ + return data; +} + +void operator delete(void *ptr) noexcept { + kFree(ptr); +} diff --git a/memory/paging.c b/memory/paging.c new file mode 100644 index 0000000..6a0acfe --- /dev/null +++ b/memory/paging.c @@ -0,0 +1,22 @@ +#pragma once + +#include "../main.h" +#include "memory.h" +#include "../runtime/panic_assert.h" +#include "../runtime/stdio.h" + +#include "paging_internal.h" + + +void *kMemoryMap(void *desiredVirtual, int pageCount, int protectionFlags, int flags, int fd) { +} + +void kMemoryUnmap(void *pageStart, int pageCount) { +} + +void *kMalloc(size_t size) { + return NULL; +} + +void kFree(void *data) { +} diff --git a/memory/paging_init.c b/memory/paging_init.c new file mode 100644 index 0000000..3564554 --- /dev/null +++ b/memory/paging_init.c @@ -0,0 +1,212 @@ + +#include "../main.h" +#include "memory.h" +#include "../runtime/panic_assert.h" +#include "../runtime/stdio.h" +#include "../graphics/graphics.h" +#include "../kernel/kmain.h" +#include "../interrupt/interrupt.h" +#include "../execformat/pe/reloc.h" +void execformat_pe_ReadSystemHeader(execformat_pe_PortableExecutable *pe); + +#include +#include + +#include "paging_internal.h" + + +EFI_MEMORY_DESCRIPTOR *efiMemoryMap; +UINTN efiMemoryMapSize; +UINTN efiMemoryMapKey; +UINTN efiDescriptorSize; +UINT32 efiDescriptorVertion; + +uint64_t paging_TotalBytes, paging_UsableBytes; +bool paging_SupportExecuteDisable; +uint64_t paging_EndPhysicalAddress; // past-the-end marker (and length) for physical memory +int paging_EndPhysicalPage; // past-the-end for physical pages (EndPhysicalAddress/SYSTEM_PAGE_SIZE) +uint64_t paging_PML4Table[512] ALIGNED(4096); // Kernel-mode virtual memory paging directory pointer table (Level 4 paging) +uint64_t paging_LoaderCodeAddress, paging_LoaderCodeSize; // physical address for loader code section +int paging_LoaderCodePageCount; // page count for loader code section + +void runtime_InitPaging() { + // TODO Obtain Execute Disable support status instead of assumpting its existence + paging_SupportExecuteDisable = true; + + // obtain the UEFI memory mapping + EFI_STATUS status; + + efiMemoryMapSize = 0; + efiMemoryMap = NULL; + status = efiBootServices->GetMemoryMap( + &efiMemoryMapSize, + efiMemoryMap, + &efiMemoryMapKey, + &efiDescriptorSize, + &efiDescriptorVertion); + assert(status == EFI_BUFFER_TOO_SMALL && "What? An empty buffer is not too small?"); + + efiMemoryMapSize += 2 * sizeof(EFI_MEMORY_DESCRIPTOR); + efiMemoryMap = (EFI_MEMORY_DESCRIPTOR *)efiMalloc(efiMemoryMapSize); + assert(efiMemoryMap && "efiMemoryMap allocate failed"); + status = efiBootServices->GetMemoryMap( + &efiMemoryMapSize, + efiMemoryMap, + &efiMemoryMapKey, + &efiDescriptorSize, + &efiDescriptorVertion); + assert(!EFI_ERROR(status) && "GetMemoryMap() with buffer allocated failed"); + + io_Printf(" .text: [%08x-%08x] len=%d (%d pages)\n", link_TextStart, link_TextEnd, link_TextEnd - link_TextStart, roundUpToPageCount(link_TextEnd - link_TextStart)); + io_Printf(" .data: [%08x-%08x] len=%d (%d pages)\n", link_DataStart, link_DataEnd, link_DataEnd - link_DataStart, roundUpToPageCount(link_DataEnd - link_DataStart)); + io_Printf(".rodata: [%08x-%08x] len=%d (%d pages)\n", link_RodataStart, link_RodataEnd, link_RodataEnd - link_RodataStart, roundUpToPageCount(link_RodataEnd - link_RodataStart)); + io_Printf(" .bss: [%08x-%08x] len=%d (%d pages)\n\n", link_BssStart, link_BssEnd, link_BssEnd - link_BssStart, roundUpToPageCount(link_BssEnd - link_BssStart)); + + // iterate the listing, accumlate counters and print info + paging_LoaderCodeAddress = paging_TotalBytes = paging_UsableBytes = 0; + memset(paging_physical_Bitmap, 0xff, sizeof(paging_physical_Bitmap)); + io_WriteConsoleASCII("EFI Memory mapping:\n"); + for (EFI_MEMORY_DESCRIPTOR *entry = efiMemoryMap; + (char *)entry < (char *)efiMemoryMap + efiMemoryMapSize; + entry = NEXT_MEMORY_DESCRITOR(entry, efiDescriptorSize) { + io_Printf( + " [%08x-%08x] -> [%08x] %s (%d)\n", + entry->PhysicalStart, + entry->PhysicalStart + entry->NumberOfPages * SYSTEM_PAGE_SIZE, + entry->VirtualStart, + memoryTypeName(entry->Type), + entry->Type); + paging_TotalBytes += SYSTEM_PAGE_SIZE * entry->NumberOfPages; + if (entry->Type == EfiConventionalMemory) { + // TODO include EfiBootServicesCode/Data as usable + paging_physical_BitmapWriteZero( + entry->PhysicalStart / SYSTEM_PAGE_SIZE, + entry->PhysicalStart / SYSTEM_PAGE_SIZE + entry->NumberOfPages); + paging_UsableBytes += SYSTEM_PAGE_SIZE * entry->NumberOfPages; + } else // page unusable + /*paging_physical_BitmapWriteOne( + entry->PhysicalStart / SYSTEM_PAGE_SIZE, + entry->PhysicalStart / SYSTEM_PAGE_SIZE + entry->NumberOfPages);*/ + ; + if (entry->Type == EfiLoaderCode) { + assert(!paging_LoaderCodeAddress && "Two EfiLoaderCode mappings at the same time"); + paging_LoaderCodeAddress = entry->PhysicalStart; + paging_LoaderCodeSize = entry->NumberOfPages * SYSTEM_PAGE_SIZE; + paging_LoaderCodePageCount = entry->NumberOfPages; + } + if (paging_EndPhysicalAddress < entry->PhysicalStart + entry->NumberOfPages * SYSTEM_PAGE_SIZE) + paging_EndPhysicalAddress = entry->PhysicalStart + entry->NumberOfPages * SYSTEM_PAGE_SIZE; + } + paging_EndPhysicalPage = paging_EndPhysicalAddress / SYSTEM_PAGE_SIZE; + + io_Printf( + " Total memory: %llu (%.2lf MB, %.2lf GB), EndPhyAddr %08llx\n", + paging_TotalBytes, + paging_TotalBytes / 1024.0 / 1024.0, + paging_TotalBytes / 1024.0 / 1024.0 / 1024.0, + paging_EndPhysicalAddress); + io_Printf( + "Usable memory: %llu (%.2lf MB, %.2lf GB)\n", + paging_UsableBytes, + paging_UsableBytes / 1024.0 / 1024.0, + paging_UsableBytes / 1024.0 / 1024.0 / 1024.0); + + io_PauseForKeystroke(); + + assert(paging_LoaderCodeAddress && "EfiLoaderCode mapping not found"); + + io_WriteConsoleASCII("Mapping kernel memory:\n"); + // map kernel code + io_Printf(" .Text... %d 4K pages\n",roundUpToPageCount(link_TextEnd-link_TextStart)); + paging_map_Page( // map .text + (uint64_t)link_TextStart, + KERNEL_CODE_VIRTUAL + ((uint64_t)link_TextStart - paging_LoaderCodeAddress), + roundUpToPageCount(link_TextEnd - link_TextStart), + MAP_PROT_READ | MAP_PROT_EXEC); + io_Printf(" .Data... %d 4K pages\n",roundUpToPageCount(link_DataEnd-link_DataStart)); + paging_map_Page( // map .data + (uint64_t)link_DataStart, + KERNEL_CODE_VIRTUAL + ((uint64_t)link_DataStart - paging_LoaderCodeAddress), + roundUpToPageCount(link_DataEnd - link_DataStart), + MAP_PROT_READ | MAP_PROT_WRITE); + io_Printf(" .Rodata... %d 4K pages\n",roundUpToPageCount(link_RodataEnd-link_RodataStart)); + paging_map_Page( // map .rodata + (uint64_t)link_RodataStart, + KERNEL_CODE_VIRTUAL + ((uint64_t)link_RodataStart - paging_LoaderCodeAddress), + roundUpToPageCount(link_RodataEnd - link_RodataStart), + MAP_PROT_READ); + io_Printf(" .Bss... %d 4K pages\n",roundUpToPageCount(link_BssEnd-link_BssStart)); + paging_map_Page( // map .bss + (uint64_t)link_BssStart, + KERNEL_CODE_VIRTUAL + ((uint64_t)link_BssStart - paging_LoaderCodeAddress), + roundUpToPageCount(link_BssEnd - link_BssStart), + MAP_PROT_READ | MAP_PROT_WRITE); + //paging_map_Page(paging_LoaderCodeAddress,KERNEL_CODE_VIRTUAL,paging_LoaderCodePageCount,MAP_PROT_READ|MAP_PROT_WRITE|MAP_PROT_EXEC); + + // map other VM data + io_Printf(" Framebuffer... %d 2M pages\n",roundUpToPageCount2M(graphics_FramebufferSize)); + paging_map_Page2M( // map the framebuffer output + (uint64_t)graphics_DeviceFramebuffer, + KERNEL_FRAMEBUFFER_MAPPING, + roundUpToPageCount2M(graphics_FramebufferSize), + MAP_PROT_READ | MAP_PROT_WRITE); + io_Printf(" Physical... %d 2M pages\n",roundUpToPageCount2M(paging_EndPhysicalAddress)); + paging_map_Page2M( // map the physical memory + 0, + 0, + roundUpToPageCount2M(paging_EndPhysicalAddress), + MAP_PROT_READ | MAP_PROT_WRITE | MAP_PROT_EXEC); + io_Printf(" Stack\n"); + paging_map_Page(paging_physical_AllocateOneFrame(),KERNEL_STACK_END_VIRTUAL-SYSTEM_PAGE_SIZE,1,MAP_PROT_READ|MAP_PROT_WRITE); + /*paging_map_Page2M( // stack, allocate a fresh new 2M + paging_physical_AllocateOneFrame2M(), + KERNEL_STACK_END_VIRTUAL - KERNEL_STACK_INITIAL_SIZE, + KERNEL_STACK_INITIAL_SIZE / SYSTEM_PAGE_2M_SIZE, + MAP_PROT_READ | MAP_PROT_WRITE);*/ + kMain_StackPosition = KERNEL_STACK_END_VIRTUAL; + /*paging_map_PageAllocated( + KERNEL_MISC_MAPPING, + roundUpToPageCount(KERNEL_MISC_SIZE), + MAP_PROT_READ|MAP_PROT_WRITE);*/ + io_Printf(" Misc... %d 2M pages\n",roundUpToPageCount2M(KERNEL_MISC_SIZE)); + paging_map_PageAllocated2M( // misc data + KERNEL_MISC_MAPPING, + roundUpToPageCount2M(KERNEL_MISC_SIZE), + MAP_PROT_READ|MAP_PROT_WRITE); + //paging_map_PageAllocated(KERNEL_HEAP_VIRTUAL,512,MAP_PROT_READ|MAP_PROT_WRITE); + + io_WriteConsoleASCII("Mapping completed\n"); + + + + // woohoo byebye! + efiBootServices->ExitBootServices(efiImageHandle, efiMemoryMapKey); + io_WriteConsoleASCII("Goodbye BootServices!\n"); + + // so now we're in unmanaged mode, we need to set up heap and stack, and jump to the new entry point kMain_Init. + // disable interrupts asap + asm volatile("cli":::"memory"); + interrupt_Enabled=false; + + // set the new virtual memory mapping + if (paging_SupportExecuteDisable) + paging_modeswitch_4LevelPagingNX(paging_PML4Table, 0); + else + paging_modeswitch_4LevelPaging(paging_PML4Table, 0); + graphics_DeviceFramebuffer = (void *)KERNEL_FRAMEBUFFER_MAPPING; + io_WriteConsoleASCII("Virtual Memory mapping switched\n"); + + // relocate the hardcoded symbols + execformat_pe_PortableExecutable pe; + execformat_pe_ReadSystemHeader(&pe); + execformat_pe_BaseRelocate(&pe, (void *)link_RelocStart, (void *)link_RelocEnd, paging_LoaderCodeAddress, KERNEL_CODE_VIRTUAL); + + io_WriteConsoleASCII("Relocation OK\n"); + + // find the symbol kMain_Init + //uint64_t target_kmain = KERNEL_CODE_VIRTUAL + ((uint64_t)kMain_Init - paging_LoaderCodeAddress); + // call it, once and for all + //((kMainType)target_kmain)(); + kMain_Init(); + __builtin_unreachable(); // execution cannot reach here +} diff --git a/memory/paging_internal.h b/memory/paging_internal.h new file mode 100644 index 0000000..a012bd1 --- /dev/null +++ b/memory/paging_internal.h @@ -0,0 +1,138 @@ +#pragma once + +#include "memory.h" +#include "stdbool.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +// defined in paging_init.c +extern EFI_MEMORY_DESCRIPTOR *efiMemoryMap; +extern UINTN efiMemoryMapSize; +extern UINTN efiMemoryMapKey; +extern UINTN efiDescriptorSize; +extern UINT32 efiDescriptorVertion; + +// defined in paging_init.c +extern uint64_t paging_TotalBytes, paging_UsableBytes; +extern bool paging_SupportExecuteDisable; +extern uint64_t paging_EndPhysicalAddress; // past-the-end marker (and length) for physical memory +extern int paging_EndPhysicalPage; // past-the-end for physical pages (EndPhysicalAddress/SYSTEM_PAGE_SIZE) +extern uint64_t paging_PML4Table[512]; // Kernel-mode virtual memory paging directory pointer table (PAE/Level 4 paging) +extern uint64_t paging_LoaderCodeAddress; // physical address for loader code section +extern int paging_LoaderCodePageCount; // page count for loader code section + +// defined in paging_physical.c +#define BITMAP_BITS 64 +extern uint64_t paging_physical_Bitmap[MAX_SYSTEM_MEMORY_PAGES / BITMAP_BITS]; // mapped with Bitmap[i/64] | 1<<(i%64), unlike convention +void paging_physical_BitmapWriteOne(int begin, int end); +void paging_physical_BitmapWriteZero(int begin, int end); +// these functions do not do any bookkeeping so use with care +uint64_t paging_physical_AllocateOneFrame(); // zeros the returned page +uint64_t paging_physical_AllocateOneFrame2M(); // zeros the returned page; this is 512 normal frames +int paging_physical_AllocateFrames(int pageCount, uint64_t frames[]); // allocate frames, not continuous, ret allloced cnt +int paging_physical_AllocateFrames2M(int pageCount, uint64_t frames[]); // allocate 2M frames, not continuous +void paging_physical_FreeFrame(uint64_t frame, int pageCount); // frees continuous frames in physical addr + +// defined in paging_map.c +#define PML_PRESENT (1ull << 0) +#define PML_WRITEABLE (1ull << 1) +#define PML_USER (1ull << 2) +#define PML_PAGE_WRITETHROUGH (1ull << 3) +#define PML_PAGE_CACHE_DISABLE (1ull << 4) +#define PML_ACCESSED (1ull << 5) +#define PML_DIRTY (1ull << 6) +#define PML_PAGE_SIZE (1ull << 7) +#define PML_GLOBAL (1ull << 8) +#define PML_EXECUTE_DISABLE (1ull << 63) +#define PML_ADDR_MASK (0xFFFFFFFFFF000ull) // 51:12 at max length +#define PML_ADDR_MASK_2M (0xFFFFFFFE00000ull) // 51:21 at max length +#define PML_ADDR_MASK_1G (0xFFFFFC0000000ull) // 51:30 at max length +void paging_map_Page(uint64_t physical, uint64_t virt, int pageCount, int protectionFlags); +void paging_map_Page2M(uint64_t physical, uint64_t virt, int pageCount, int protectionFlags); +void paging_map_Page1G(uint64_t physical, uint64_t virt, int pageCount, int protectionFlags); +// Allocates pageCount fresh new 4K pages with paging_physical and maps them continuously to virtual +void paging_map_PageAllocated(uint64_t virt, int pageCount, int protectionFlags); +// Allocates pageCount fresh new 2M pages with paging_physical and maps them continuously to virtual +void paging_map_PageAllocated2M(uint64_t virt, int pageCount, int protectionFlags); +// Unmaps the pages at virtual and free the underlying physical frames, with past-the-end of the memory addr +void paging_map_FreeAllocated(uint64_t virt, uint64_t virt_end); + + +// defined in paging_modeswitch.S +FASTCALL_ABI void paging_modeswitch_4LevelPaging(void *pml4, int pcid); +FASTCALL_ABI void paging_modeswitch_4LevelPagingNX(void *pml4, int pcid); // with setting the Execute-Disalbe bit +FASTCALL_ABI void paging_modeswitch_Table(void *pml, int pcid); + +static inline const char * + memoryTypeName(EFI_MEMORY_TYPE type) { +#define CASE(c) \ + case c: \ + return #c; + switch (type) { + CASE(EfiReservedMemoryType) + CASE(EfiLoaderCode) + CASE(EfiLoaderData) + CASE(EfiBootServicesCode) + CASE(EfiBootServicesData) + CASE(EfiRuntimeServicesCode) + CASE(EfiRuntimeServicesData) + CASE(EfiConventionalMemory) + CASE(EfiUnusableMemory) + CASE(EfiACPIReclaimMemory) + CASE(EfiACPIMemoryNVS) + CASE(EfiMemoryMappedIO) + CASE(EfiMemoryMappedIOPortSpace) + CASE(EfiPalCode) + case EfiMaxMemoryType: + return "EfiPersistentMemory"; + } + return "(unknown)"; +#undef CASE +} + +#ifndef NEXT_MEMORY_DESCRITOR +#define NEXT_MEMORY_DESCRITOR(desc, size) ((EFI_MEMORY_DESCRIPTOR *)((char *)desc + size))) +#endif + + +inline static uint64_t roundUpTo2Exponent(uint64_t v) { + uint64_t s = 1; + while (s < v) + s <<= 1; + return s; +} + +inline static uint64_t takeBitfield(uint64_t v, int high, int low) { + return (v >> low) & ((1 << (high - low + 1)) - 1); +} + +inline static void flush_tlb_single(uint64_t addr) { + asm volatile( + "invlpg (%0)" ::"r"(addr) + : "memory"); +} + +inline static int roundUpToPageCount(uint64_t size) { + if (size % SYSTEM_PAGE_SIZE == 0) + return size / SYSTEM_PAGE_SIZE; + return size / SYSTEM_PAGE_SIZE + 1; +} +inline static int roundUpToPageCount2M(uint64_t size) { + if (size % SYSTEM_PAGE_2M_SIZE == 0) + return size / SYSTEM_PAGE_2M_SIZE; + return size / SYSTEM_PAGE_2M_SIZE + 1; +} +inline static int roundUpToPageCount1G(uint64_t size) { + if (size % SYSTEM_PAGE_1G_SIZE == 0) + return size / SYSTEM_PAGE_1G_SIZE; + return size / SYSTEM_PAGE_1G_SIZE + 1; +} + + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/memory/paging_map.c b/memory/paging_map.c new file mode 100644 index 0000000..0bf363e --- /dev/null +++ b/memory/paging_map.c @@ -0,0 +1,189 @@ + +#include "memory.h" +#include "paging_internal.h" +#include "../runtime/panic_assert.h" +#include "string.h" + + +// some learning: +// https://forum.osdev.org/viewtopic.php?f=1&t=51392 (Confirmative question about 4-level and 5-level paging) + +void paging_map_Page(uint64_t physical, uint64_t virtual, int pageCount, int protectionFlags) { + assert(physical % SYSTEM_PAGE_SIZE == 0 && "Physical address not page-aligned"); + assert(virtual % SYSTEM_PAGE_SIZE == 0 && "Virtual address not page-aligned"); + + while (pageCount--) { + // PML4E index pointing to a PML3 table (Page-Directory-Pointer Table) + uint64_t *table = paging_PML4Table; + uint64_t i = takeBitfield(virtual, 47, 39); + if ((table[i] & PML_PRESENT) == 0) // allocate a new page as PML3 table + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // PML3E(PDPTE) index pointing to a PML2 table (Page-Directory) + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 38, 30); + if ((table[i] & PML_PRESENT) == 0) // allocate page as page directory + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // PML2E(PD) index pointing to a PML1 table (Page Table) + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 29, 21); + if ((table[i] & PML_PRESENT) == 0) // allocate page as page table + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // Finally, the page table. + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 20, 12); + + table[i] = physical | PML_PRESENT | + ((protectionFlags & MAP_PROT_WRITE) ? PML_WRITEABLE : 0) | + ((paging_SupportExecuteDisable && !(protectionFlags & MAP_PROT_EXEC)) ? PML_EXECUTE_DISABLE : 0); + + + flush_tlb_single(virtual); + physical += SYSTEM_PAGE_SIZE; + virtual += SYSTEM_PAGE_SIZE; + } +} + +void paging_map_Page2M(uint64_t physical, uint64_t virtual, int pageCount, int protectionFlags) { + assert(physical % SYSTEM_PAGE_2M_SIZE == 0 && "Physical address not page-aligned"); + assert(virtual % SYSTEM_PAGE_2M_SIZE == 0 && "Virtual address not page-aligned"); + + while (pageCount--) { + // PML4E index pointing to a PML3 table (Page-Directory-Pointer Table) + uint64_t *table = paging_PML4Table; + uint64_t i = takeBitfield(virtual, 47, 39); + if ((table[i] & PML_PRESENT) == 0) // allocate a new page as PML3 table + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // PML3E(PDPTE) index pointing to a PML2 table (Page-Directory) + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 38, 30); + if ((table[i] & PML_PRESENT) == 0) // allocate page as page directory + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // PML2E(PD) index pointing to a PML1 table (Page Table) + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 29, 21); + if ((table[i] & PML_PRESENT) != 0 && (table[i] & PML_PAGE_SIZE) == 0) // deallocate the page if present + paging_physical_FreeFrame(table[i] & PML_ADDR_MASK, 1); + + // 2MB pages in Page Tables + table[i] = physical | PML_PRESENT | PML_PAGE_SIZE | + ((protectionFlags & MAP_PROT_WRITE) ? PML_WRITEABLE : 0) | + ((paging_SupportExecuteDisable && !(protectionFlags & MAP_PROT_EXEC)) ? PML_EXECUTE_DISABLE : 0); + + + flush_tlb_single(virtual); + physical += SYSTEM_PAGE_2M_SIZE; + virtual += SYSTEM_PAGE_2M_SIZE; + } +} + +void paging_map_Page1G(uint64_t physical, uint64_t virtual, int pageCount, int protectionFlags) { + assert(physical % SYSTEM_PAGE_1G_SIZE == 0 && "Physical address not page-aligned"); + assert(virtual % SYSTEM_PAGE_1G_SIZE == 0 && "Virtual address not page-aligned"); + + while (pageCount--) { + // PML4E index pointing to a PML3 table (Page-Directory-Pointer Table) + uint64_t *table = paging_PML4Table; + uint64_t i = takeBitfield(virtual, 47, 39); + if ((table[i] & PML_PRESENT) == 0) // allocate a new page as PML3 table + table[i] = paging_physical_AllocateOneFrame() | PML_PRESENT | PML_WRITEABLE; + + // PML3E(PDPTE) index pointing to a PML2 table (Page-Directory) + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 38, 30); + if ((table[i] & PML_PRESENT) != 0 && (table[i] & PML_PAGE_SIZE) == 0) // deallocate the page if present + paging_physical_FreeFrame(table[i] & PML_ADDR_MASK, 1); + + // 1GB pages in Page Directories + table[i] = physical | PML_PRESENT | PML_PAGE_SIZE | + ((protectionFlags & MAP_PROT_WRITE) ? PML_WRITEABLE : 0) | + ((paging_SupportExecuteDisable && !(protectionFlags & MAP_PROT_EXEC)) ? PML_EXECUTE_DISABLE : 0); + + + flush_tlb_single(virtual); + physical += SYSTEM_PAGE_1G_SIZE; + virtual += SYSTEM_PAGE_1G_SIZE; + } +} + +void paging_map_PageAllocated(uint64_t virtual, int pageCount, int protectionFlags) { + assert(virtual % SYSTEM_PAGE_SIZE == 0 && "Virtual address not page-aligned"); + + // skip the first 1M + int i = 1024 / 4 / BITMAP_BITS; + while (pageCount--) { + uint64_t freshPage = 0; + + // this code is in sync with paging_physical.c, paging_physical_AllocateOneFrame, so you have to modify both + for (; i < paging_EndPhysicalPage / BITMAP_BITS; i++) + if (paging_physical_Bitmap[i] != ~0ull) + for (int j = 0; j < BITMAP_BITS; j++) + if ((paging_physical_Bitmap[i] & (1ull << j)) == 0) { + paging_physical_Bitmap[i] |= (1ull << j); + freshPage = (((uint64_t)i) * BITMAP_BITS + j) * SYSTEM_PAGE_SIZE; + memset((void *)freshPage, 0, SYSTEM_PAGE_SIZE); + } + + paging_map_Page(freshPage, virtual, 1, protectionFlags); + virtual += SYSTEM_PAGE_SIZE; + } +} + +void paging_map_PageAllocated2M(uint64_t virtual, int pageCount, int protectionFlags) { + assert(pageCount > HELOS_BUFFER_SIZE / SYSTEM_PAGE_2M_SIZE * 8 && "helos_Buffer unable to hold all pointers"); + assert(virtual % SYSTEM_PAGE_2M_SIZE == 0 && "Virtual address not page-aligned"); + + uint64_t *buf = (uint64_t *)Buffer; + + int allocated = paging_physical_AllocateFrames2M(pageCount, buf); + for (int i = 0; i < allocated; i++) + paging_map_Page2M(buf[i], virtual + SYSTEM_PAGE_2M_SIZE * i, 1, protectionFlags); +} + +void paging_map_FreeAllocated(uint64_t virtual, uint64_t end) { + assert(virtual % SYSTEM_PAGE_SIZE == 0 && "Virtual address not page-aligned"); + + while (virtual < end) { + uint64_t *table = paging_PML4Table; + uint64_t i = takeBitfield(virtual, 47, 39); + if ((table[i] & PML_PRESENT) == 0) + goto loop_end; + + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 38, 30); + if ((table[i] & PML_PRESENT) == 0) + goto loop_end; + if (table[i] & PML_PAGE_SIZE) { // 1G mapping + paging_physical_FreeFrame(table[i] & PML_ADDR_MASK_1G, 512 * 512); + table[i] = 0; + virtual += SYSTEM_PAGE_1G_SIZE; + continue; + } + + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 29, 21); + if ((table[i] & PML_PRESENT) == 0) + goto loop_end; + if (table[i] & PML_PAGE_SIZE) { // 2M mapping + paging_physical_FreeFrame(table[i] & PML_ADDR_MASK_2M, 512); + table[i] = 0; + virtual += SYSTEM_PAGE_2M_SIZE; + continue; + } + + table = (uint64_t *)(table[i] & PML_ADDR_MASK); + i = takeBitfield(virtual, 20, 12); + + if (table[i] & PML_PRESENT) { + paging_physical_FreeFrame(table[i] & PML_ADDR_MASK, 1); + table[i] = 0; + } + +loop_end: + virtual += SYSTEM_PAGE_SIZE; + } +} diff --git a/memory/paging_modeswitch.S b/memory/paging_modeswitch.S new file mode 100644 index 0000000..aa94fe3 --- /dev/null +++ b/memory/paging_modeswitch.S @@ -0,0 +1,86 @@ +format elf64 + + +section '.text' executable + +; Details on Control Registers and MSRs can be found here: +; https://wiki.osdev.org/CPU_Registers_x86-64 + +; x64fastcall void paging_modeswitch_4LevelPaging(void* pml4, int pcid) +; +; This function assumes that the program is now in 64-bit mode (long mode). +; Paging(CR0.PG) in general and long mode (MSR EFER.LME) must already be enabled in this state. +; +; Input: (void* rcx, int rdx) +; Clobbers: rax, flags +public paging_modeswitch_4LevelPaging +paging_modeswitch_4LevelPaging: + ; 4 Level paging: CR0.PG (bit 31) = 1 (Protected mode enable) + ; CR4.PAE (bit 05) = 1 (PAE enable) + ; MSR IA32_EFER.LME (bit 10) = 1 (IA32e 64-bit mode enable) + ; CR4.LA57 (bit 12) = 0 (4-level paging instead of 5) + ; We only need to set CR4.LA57 + ; Let's also set CR4.PCIDE(bit 17)=1, enabling process-context identifiers + mov rax, cr0 + and rax, 0xFFFFFFFFFFFFEFFF ; unset CR4.LA57 + or rax, 0x20000 ; set CR4.PCIDE + mov cr0, rax + + and rdx, 0xFFF ; take only the 11:0 bits of the PCID + or rcx, rdx ; construct the full CR3 + mov cr3, rcx ; set CR3, invalidate all TLB cache + ret + +; x64fastcall void paging_modeswitch_4LevelPagingNX(void* pml4, int pcid) +; +; This function assumes that the program is now in 64-bit mode (long mode). +; Paging(CR0.PG) in general and long mode (MSR EFER.LME) must already be enabled in this state. +; +; This function also sets the IA32_EFER.NXE bit, enabling No-Execute feature. +; +; Input: (void* rcx, int rdx) +; Clobbers: rax, r8, r9, flags +public paging_modeswitch_4LevelPagingNX +paging_modeswitch_4LevelPagingNX: + ; 4 Level paging: CR0.PG (bit 31) = 1 (Protected mode enable) + ; CR4.PAE (bit 05) = 1 (PAE enable) + ; MSR IA32_EFER.LME (bit 10) = 1 (IA32e 64-bit mode enable) + ; CR4.LA57 (bit 12) = 0 (4-level paging instead of 5) + ; We only need to set CR4.LA57 + ; Let's also set CR4.PCIDE(bit 17)=1, enabling process-context identifiers + mov rax, cr0 + and rax, 0xFFFFFFFFFFFFEFFF ; unset CR4.LA57 + or rax, 0x20000 ; set CR4.PCIDE (bit 17) + mov cr0, rax + + ; save rcx and rdx, RDMSR/WRMSR uses these + mov r8, rcx + mov r9, rdx + + mov ecx, 0xC0000080 ; operate on the IA32_EFER MSR + rdmsr ; read the MSR into edx:eax + or eax, (1 shl 11) ; set No-Execute Enable (bit 11) + wrmsr ; write the MSR back + + ; restore rcx and rdx + mov rcx, r8 + mov rdx, r9 + + and rdx, 0xFFF ; take only the 11:0 bits of the PCID + or rcx, rdx ; construct the full CR3 + mov cr3, rcx ; set CR3, invalidate all TLB cache + ret + +; x64fastcall void paging_modeswitch_Table(void* pml, int pcid) +; +; This function simply sets CR3 and run INVLPG, flushing the TLB cache. +; +; Input: (void* rcx, int rdx) +; Clobbers: none +public paging_modeswitch_Table +paging_modeswitch_Table: + and rdx, 0xFFF ; take only the 11:0 bits of the PCID + or rcx, rdx ; construct the full CR3 + mov cr3, rcx ; set CR3, invalidate all TLB cache + ret + diff --git a/memory/paging_physical.c b/memory/paging_physical.c new file mode 100644 index 0000000..4ccb22d --- /dev/null +++ b/memory/paging_physical.c @@ -0,0 +1,136 @@ + +#include "memory.h" +#include "paging_internal.h" +#include "../runtime/stdio.h" +#include + +uint64_t paging_physical_Bitmap[MAX_SYSTEM_MEMORY_PAGES / BITMAP_BITS]; + +static inline uint64_t fillBits(int begin, int last) { + if (last == BITMAP_BITS - 1) + return ~((1ull << begin) - 1ull); + if (begin == 0) + return (1ull << (last + 1ull)) - 1ull; + return (~((1ull << begin) - 1ull)) & ((1ull << (last + 1ull)) - 1ull); +} + +void paging_physical_BitmapWriteOne(int begin, int end) { + int whereBegin = begin / BITMAP_BITS, whereLast = (end - 1) / BITMAP_BITS; + if (whereBegin == whereLast) + paging_physical_Bitmap[whereBegin] |= fillBits(begin % BITMAP_BITS, (end - 1) % BITMAP_BITS); + else { + paging_physical_Bitmap[whereBegin] |= fillBits(begin % BITMAP_BITS, BITMAP_BITS - 1); + paging_physical_Bitmap[whereLast] |= fillBits(0, (end - 1) % BITMAP_BITS); + for (int i = whereBegin + 1; i < whereLast; i++) + paging_physical_Bitmap[i] = (~0ull); + } +} + +void paging_physical_BitmapWriteZero(int begin, int end) { + int whereBegin = begin / BITMAP_BITS, whereLast = (end - 1) / BITMAP_BITS; + if (whereBegin == whereLast) + paging_physical_Bitmap[whereBegin] &= ~fillBits(begin % BITMAP_BITS, (end - 1) % BITMAP_BITS); + else { + paging_physical_Bitmap[whereBegin] &= ~fillBits(begin % BITMAP_BITS, BITMAP_BITS - 1); + paging_physical_Bitmap[whereLast] &= ~fillBits(0, (end - 1) % BITMAP_BITS); + for (int i = whereBegin + 1; i < whereLast; i++) + paging_physical_Bitmap[i] = 0; + } +} + +static int phy_i = 1024 / 4 / BITMAP_BITS; + +uint64_t paging_physical_AllocateOneFrame() { + // skip the first 1M + /*for (; phy_i < paging_EndPhysicalPage / BITMAP_BITS; phy_i++) + if (paging_physical_Bitmap[phy_i] != ~0ull) + for (int j = 0; j < BITMAP_BITS; j++) + if ((paging_physical_Bitmap[phy_i] & (1ull << j)) == 0) { + paging_physical_Bitmap[phy_i] |= (1ull << j); + uint64_t addr = (((uint64_t)phy_i) * BITMAP_BITS + j) * SYSTEM_PAGE_SIZE; + memset((void *)addr, 0, SYSTEM_PAGE_SIZE); + return addr; + }*/ + uint64_t addr; + paging_physical_AllocateFrames(1, &addr); + return addr; +} + +// skip the first 2M +static int phy_i_2M = 2048 / 4 / BITMAP_BITS; + +uint64_t paging_physical_AllocateOneFrame2M() { + // skip the first 2M + /*for (; phy_i_2M < paging_EndPhysicalPage / BITMAP_BITS; phy_i_2M += 8) { + for (int j = 0; j < 8; j++) + if (paging_physical_Bitmap[phy_i_2M + j] != 0) + goto for_end; + + // now here we have a whole chunk at [i, i+8) + for (int j = 0; j < 8; j++) + paging_physical_Bitmap[phy_i_2M + j] = ~0ull; + uint64_t addr = (((uint64_t)phy_i_2M) * BITMAP_BITS) * SYSTEM_PAGE_SIZE; + memset((void *)addr, 0, SYSTEM_PAGE_2M_SIZE); + return addr; +for_end:; + }*/ + uint64_t addr; + paging_physical_AllocateFrames2M(1, &addr); + return addr; +} + +int paging_physical_AllocateFrames(int pageCount, uint64_t frames[]) { + // this code is in sync with paging_map.c, paging_map_PageAllocated, so you have to modify both + int page = 0; + for (; page < pageCount; page++) { + frames[page] = 0; + for (; phy_i < paging_EndPhysicalPage / BITMAP_BITS; phy_i++) + if (paging_physical_Bitmap[phy_i] != ~0ull) + for (int j = 0; j < BITMAP_BITS; j++) + if ((paging_physical_Bitmap[phy_i] & (1ull << j)) == 0) { + paging_physical_Bitmap[phy_i] |= (1ull << j); + uint64_t addr = (((uint64_t)phy_i) * BITMAP_BITS + j) * SYSTEM_PAGE_SIZE; + memset((void *)addr, 0, SYSTEM_PAGE_SIZE); + frames[page] = addr; + goto for_end; // break the phy_i loop + } + if (frames[page] == 0) + break; +for_end:; + } + + return page; +} + +int paging_physical_AllocateFrames2M(int pageCount, uint64_t frames[]) { + int page = 0; + for (; page < pageCount; page++) { + frames[page] = 0; + for (; phy_i_2M < paging_EndPhysicalPage / BITMAP_BITS; phy_i_2M += 8) { + for (int j = 0; j < 8; j++) + if (paging_physical_Bitmap[phy_i_2M + j] != 0) + goto for_end; // continue the outer for loop + + // now here we have a whole chunk at [i, i+8) + for (int j = 0; j < 8; j++) + paging_physical_Bitmap[phy_i_2M + j] = ~0ull; + uint64_t addr = (((uint64_t)phy_i_2M) * BITMAP_BITS) * SYSTEM_PAGE_SIZE; + memset((void *)addr, 0, SYSTEM_PAGE_2M_SIZE); + + frames[page] = addr; + break; +for_end:; + } + if (frames[page] == 0) + break; + } + return page; +} + +void paging_physical_FreeFrame(uint64_t frame, int pageCount) { + if (frame % SYSTEM_PAGE_SIZE != 0) { + io_Printf("paging_physical_FreeFrame: frame %08llx is not aligned\n", frame); + return; + } + paging_physical_BitmapWriteZero(frame / SYSTEM_PAGE_SIZE, frame / SYSTEM_PAGE_SIZE + pageCount); +} diff --git a/memory/test_fillbits.c b/memory/test_fillbits.c new file mode 100644 index 0000000..81523fb --- /dev/null +++ b/memory/test_fillbits.c @@ -0,0 +1,38 @@ +#include +#include + +static inline uint64_t fillBits(int begin, int last) { + if (last == 63) + return ~((1ull << begin) - 1ull); + if (begin == 0) + return (1ull << (last + 1ull)) - 1ull; + return (~((1ull << begin) - 1ull)) & ((1ull << (last + 1ull)) - 1ull); +} + +void printit(int a, int b) { + printf("[%d,%d] = %08lx\n", a, b, fillBits(a, b)); +} + +int main() { + printit(0, 63); + printit(1, 63); + printit(2, 63); + printit(3, 63); + printit(4, 63); + printit(5, 63); + printit(6, 63); + printit(7, 63); + printit(8, 63); + printit(9, 63); + printit(10, 63); + printit(7, 62); + printit(8, 61); + printit(9, 60); + printit(10, 59); + printit(10, 58); + printit(10, 57); + printit(10, 56); + printit(10, 55); + printit(10, 54); + printit(4, 4); +} diff --git a/memory/test_take_bitfield.c b/memory/test_take_bitfield.c new file mode 100644 index 0000000..7ccfefb --- /dev/null +++ b/memory/test_take_bitfield.c @@ -0,0 +1,16 @@ + +#include +#include + +inline static uint64_t takeBitfield(uint64_t v, int high, int low) { + return (v >> low) & ((1 << (high - low + 1)) - 1); +} + +int main() { + uint64_t val = 0x400CD696E37AE; + printf("val = %lX\n", val); + printf("val[21:5] = %lX\n", takeBitfield(val, 21, 5)); + printf("val[33:12] = %lX\n", takeBitfield(val, 33, 12)); + printf("val[21:5] = %lX\n", takeBitfield(val, 21, 5)); + printf("val[21:5] = %lX\n", takeBitfield(val, 21, 5)); +} diff --git a/run.cmd b/run.cmd new file mode 100644 index 0000000..424cfd8 --- /dev/null +++ b/run.cmd @@ -0,0 +1,4 @@ + +qemu-system-x86_64 -bios ../OVMF.fd -drive file=fat:rw:../FAT,format=raw,media=disk -m 2G + + diff --git a/runtime/calling_convention.S b/runtime/calling_convention.S new file mode 100644 index 0000000..c7afaba --- /dev/null +++ b/runtime/calling_convention.S @@ -0,0 +1,53 @@ +format elf64 + +section '.text' executable + +; x64sysvcall int sysv_x64fastcall(void* addr, int numArgs, long args1, args2, args3, args4) +; +; Calls Microsoft x64 ABI functions under System V AMD64 ABI. +; +; This function can handle ONLY up to FOUR arguments. +; numArgs is in fact unused. +; +; Input: (void* rdi, int rsi, long rdx, rcx, r8, r9) +; Output: int rax +; Clobbers: flags +public sysv_x64fastcall +sysv_x64fastcall: + sub rsp, 4*8 ; reserve the 4*8 bytes of shadow space + + ; So, before we start, let's make a chart! + ; + ; | SysVx64 | MSx64 | + ; ----+---------------+---------------+ + ; RAX | Return Value | Return Value | + ; RBX | Callee Saved | Callee Saved | + ; RCX | Argument 4 | Argument 1 | + ; RDX | Argument 3 | Argument 2 | + ; RSI | Argument 2 | Callee Saved | + ; RDI | Argument 1 | Callee Saved | + ; RBP | Callee Saved | Callee Saved | + ; RSP | Stack Pointer | Stack Pointer | + ; R8 | Argument 5 | Argument 3 | + ; R9 | Argument 6 | Argument 4 | + ; R10 | Caller Saved | Caller Saved | + ; R11 | Caller Saved | Caller Saved | + ; R12 | Callee Saved | Callee Saved | + ; R13 | Callee Saved | Callee Saved | + ; R14 | Callee Saved | Callee Saved | + ; R15 | Callee Saved | Callee Saved | + ; + ; To sum up, all we need to do is: + ; - RCX = RDX; RDX = RCX; and that's it! + ; Other stuff correspond quite well actually. + + mov rax, rcx + mov rcx, rdx + mov rdx, rax + + call rdi + + add rsp, 4*8 ; pop the shadow space + ret + + diff --git a/runtime/calling_convention.h b/runtime/calling_convention.h new file mode 100644 index 0000000..96538d0 --- /dev/null +++ b/runtime/calling_convention.h @@ -0,0 +1,12 @@ + +// x64sysvcall int sysv_x64fastcall(void* addr, int numArgs, long args1, args2, args3, args4) +// +// Calls Microsoft x64 ABI functions under System V AMD64 ABI. +// +// This function can handle ONLY up to FOUR arguments. +// numArgs is in fact unused. +// +// Input: (void* rdi, int rsi, long rdx, rcx, r8, r9) +// Output: int rax +// Clobbers: flags +__attribute__((sysv_abi)) long sysv_x64fastcall(void *addr, int numArgs, long args1, long args2, long args3, long args4); diff --git a/runtime/memcpy.c b/runtime/memcpy.c new file mode 100644 index 0000000..15b7fae --- /dev/null +++ b/runtime/memcpy.c @@ -0,0 +1,342 @@ +/******************************************************************** + ** File: memcpy.c + ** + ** Copyright (C) 1999-2010 Daniel Vik + ** + ** This software is provided 'as-is', without any express or implied + ** warranty. In no event will the authors be held liable for any + ** damages arising from the use of this software. + ** Permission is granted to anyone to use this software for any + ** purpose, including commercial applications, and to alter it and + ** redistribute it freely, subject to the following restrictions: + ** + ** 1. The origin of this software must not be misrepresented; you + ** must not claim that you wrote the original software. If you + ** use this software in a product, an acknowledgment in the + ** use this software in a product, an acknowledgment in the + ** product documentation would be appreciated but is not + ** required. + ** + ** 2. Altered source versions must be plainly marked as such, and + ** must not be misrepresented as being the original software. + ** + ** 3. This notice may not be removed or altered from any source + ** distribution. + ** + ** + ** Description: Implementation of the standard library function memcpy. + ** This implementation of memcpy() is ANSI-C89 compatible. + ** + ** The following configuration options can be set: + ** + ** LITTLE_ENDIAN - Uses processor with little endian + ** addressing. Default is big endian. + ** + ** PRE_INC_PTRS - Use pre increment of pointers. + ** Default is post increment of + ** pointers. + ** + ** INDEXED_COPY - Copying data using array indexing. + ** Using this option, disables the + ** PRE_INC_PTRS option. + ** + ** MEMCPY_64BIT - Compiles memcpy for 64 bit + ** architectures + ** + ** + ** Best Settings: + ** + ** Intel x86: LITTLE_ENDIAN and INDEXED_COPY + ** + *******************************************************************/ + + +/******************************************************************** + ** Configuration definitions. + *******************************************************************/ + +#define LITTLE_ENDIAN +#define INDEXED_COPY +#define MEMCPY_64BIT + + +/******************************************************************** + ** Includes for size_t definition + *******************************************************************/ + +#include + + +/******************************************************************** + ** Typedefs + *******************************************************************/ + +typedef unsigned char UInt8; +typedef unsigned short UInt16; +typedef unsigned int UInt32; +#ifdef _WIN32 +typedef unsigned __int64 UInt64; +#else +typedef unsigned long long UInt64; +#endif + +#ifdef MEMCPY_64BIT +typedef UInt64 UIntN; +#define TYPE_WIDTH 8L +#else +typedef UInt32 UIntN; +#define TYPE_WIDTH 4L +#endif + + +/******************************************************************** + ** Remove definitions when INDEXED_COPY is defined. + *******************************************************************/ + +#if defined(INDEXED_COPY) +#if defined(PRE_INC_PTRS) +#undef PRE_INC_PTRS +#endif /*PRE_INC_PTRS*/ +#endif /*INDEXED_COPY*/ + + +/******************************************************************** + ** Definitions for pre and post increment of pointers. + *******************************************************************/ + +#if defined(PRE_INC_PTRS) + +#define START_VAL(x) (x)-- +#define INC_VAL(x) *++(x) +#define CAST_TO_U8(p, o) ((UInt8 *)p + o + TYPE_WIDTH) +#define WHILE_DEST_BREAK (TYPE_WIDTH - 1) +#define PRE_LOOP_ADJUST -(TYPE_WIDTH - 1) +#define PRE_SWITCH_ADJUST +1 + +#else /*PRE_INC_PTRS*/ + +#define START_VAL(x) +#define INC_VAL(x) *(x)++ +#define CAST_TO_U8(p, o) ((UInt8 *)p + o) +#define WHILE_DEST_BREAK 0 +#define PRE_LOOP_ADJUST +#define PRE_SWITCH_ADJUST + +#endif /*PRE_INC_PTRS*/ + + +/******************************************************************** + ** Definitions for endians + *******************************************************************/ + +#if defined(LITTLE_ENDIAN) + +#define SHL >> +#define SHR << + +#else /* LITTLE_ENDIAN */ + +#define SHL << +#define SHR >> + +#endif /* LITTLE_ENDIAN */ + + +/******************************************************************** + ** Macros for copying words of different alignment. + ** Uses incremening pointers. + *******************************************************************/ + +#define CP_INCR() \ + { \ + INC_VAL(dstN) = INC_VAL(srcN); \ + } + +#define CP_INCR_SH(shl, shr) \ + { \ + dstWord = srcWord SHL shl; \ + srcWord = INC_VAL(srcN); \ + dstWord |= srcWord SHR shr; \ + INC_VAL(dstN) = dstWord; \ + } + + +/******************************************************************** + ** Macros for copying words of different alignment. + ** Uses array indexes. + *******************************************************************/ + +#define CP_INDEX(idx) \ + { \ + dstN[idx] = srcN[idx]; \ + } + +#define CP_INDEX_SH(x, shl, shr) \ + { \ + dstWord = srcWord SHL shl; \ + srcWord = srcN[x]; \ + dstWord |= srcWord SHR shr; \ + dstN[x] = dstWord; \ + } + + +/******************************************************************** + ** Macros for copying words of different alignment. + ** Uses incremening pointers or array indexes depending on + ** configuration. + *******************************************************************/ + +#if defined(INDEXED_COPY) + +#define CP(idx) CP_INDEX(idx) +#define CP_SH(idx, shl, shr) CP_INDEX_SH(idx, shl, shr) + +#define INC_INDEX(p, o) ((p) += (o)) + +#else /* INDEXED_COPY */ + +#define CP(idx) CP_INCR() +#define CP_SH(idx, shl, shr) CP_INCR_SH(shl, shr) + +#define INC_INDEX(p, o) + +#endif /* INDEXED_COPY */ + + +#define COPY_REMAINING(count) \ + { \ + START_VAL(dst8); \ + START_VAL(src8); \ + \ + switch (count) { \ + case 7: INC_VAL(dst8) = INC_VAL(src8); \ + case 6: INC_VAL(dst8) = INC_VAL(src8); \ + case 5: INC_VAL(dst8) = INC_VAL(src8); \ + case 4: INC_VAL(dst8) = INC_VAL(src8); \ + case 3: INC_VAL(dst8) = INC_VAL(src8); \ + case 2: INC_VAL(dst8) = INC_VAL(src8); \ + case 1: INC_VAL(dst8) = INC_VAL(src8); \ + case 0: \ + default: break; \ + } \ + } + +#define COPY_NO_SHIFT() \ + { \ + UIntN *dstN = (UIntN *)(dst8 PRE_LOOP_ADJUST); \ + UIntN *srcN = (UIntN *)(src8 PRE_LOOP_ADJUST); \ + size_t length = count / TYPE_WIDTH; \ + \ + while (length & 7) { \ + CP_INCR(); \ + length--; \ + } \ + \ + length /= 8; \ + \ + while (length--) { \ + CP(0); \ + CP(1); \ + CP(2); \ + CP(3); \ + CP(4); \ + CP(5); \ + CP(6); \ + CP(7); \ + \ + INC_INDEX(dstN, 8); \ + INC_INDEX(srcN, 8); \ + } \ + \ + src8 = CAST_TO_U8(srcN, 0); \ + dst8 = CAST_TO_U8(dstN, 0); \ + \ + COPY_REMAINING(count &(TYPE_WIDTH - 1)); \ + \ + return dest; \ + } + + +#define COPY_SHIFT(shift) \ + { \ + UIntN *dstN = (UIntN *)((((UIntN)dst8)PRE_LOOP_ADJUST) & ~(TYPE_WIDTH - 1)); \ + UIntN *srcN = (UIntN *)((((UIntN)src8)PRE_LOOP_ADJUST) & ~(TYPE_WIDTH - 1)); \ + size_t length = count / TYPE_WIDTH; \ + UIntN srcWord = INC_VAL(srcN); \ + UIntN dstWord; \ + \ + while (length & 7) { \ + CP_INCR_SH(8 * shift, 8 * (TYPE_WIDTH - shift)); \ + length--; \ + } \ + \ + length /= 8; \ + \ + while (length--) { \ + CP_SH(0, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(1, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(2, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(3, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(4, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(5, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(6, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + CP_SH(7, 8 * shift, 8 * (TYPE_WIDTH - shift)); \ + \ + INC_INDEX(dstN, 8); \ + INC_INDEX(srcN, 8); \ + } \ + \ + src8 = CAST_TO_U8(srcN, (shift - TYPE_WIDTH)); \ + dst8 = CAST_TO_U8(dstN, 0); \ + \ + COPY_REMAINING(count &(TYPE_WIDTH - 1)); \ + \ + return dest; \ + } + + +/******************************************************************** + ** + ** void *memcpy(void *dest, const void *src, size_t count) + ** + ** Args: dest - pointer to destination buffer + ** src - pointer to source buffer + ** count - number of bytes to copy + ** + ** Return: A pointer to destination buffer + ** + ** Purpose: Copies count bytes from src to dest. + ** No overlap check is performed. + ** + *******************************************************************/ + +void *memcpy(void *dest, const void *src, size_t count) { + UInt8 *dst8 = (UInt8 *)dest; + UInt8 *src8 = (UInt8 *)src; + + if (count < 8) { + COPY_REMAINING(count); + return dest; + } + + START_VAL(dst8); + START_VAL(src8); + + while (((UIntN)dst8 & (TYPE_WIDTH - 1)) != WHILE_DEST_BREAK) { + INC_VAL(dst8) = INC_VAL(src8); + count--; + } + + switch ((((UIntN)src8)PRE_SWITCH_ADJUST) & (TYPE_WIDTH - 1)) { + case 0: COPY_NO_SHIFT(); break; + case 1: COPY_SHIFT(1); break; + case 2: COPY_SHIFT(2); break; + case 3: COPY_SHIFT(3); break; +#if TYPE_WIDTH > 4 + case 4: COPY_SHIFT(4); break; + case 5: COPY_SHIFT(5); break; + case 6: COPY_SHIFT(6); break; + case 7: COPY_SHIFT(7); break; +#endif + } +} diff --git a/runtime/memcpy.h b/runtime/memcpy.h new file mode 100644 index 0000000..f3edcaa --- /dev/null +++ b/runtime/memcpy.h @@ -0,0 +1,64 @@ +/******************************************************************** + ** File: memcpy.h + ** + ** Copyright (C) 2005 Daniel Vik + ** + ** This software is provided 'as-is', without any express or implied + ** warranty. In no event will the authors be held liable for any + ** damages arising from the use of this software. + ** Permission is granted to anyone to use this software for any + ** purpose, including commercial applications, and to alter it and + ** redistribute it freely, subject to the following restrictions: + ** + ** 1. The origin of this software must not be misrepresented; you + ** must not claim that you wrote the original software. If you + ** use this software in a product, an acknowledgment in the + ** use this software in a product, an acknowledgment in the + ** product documentation would be appreciated but is not + ** required. + ** + ** 2. Altered source versions must be plainly marked as such, and + ** must not be misrepresented as being the original software. + ** + ** 3. This notice may not be removed or altered from any source + ** distribution. + ** + ** + ** Description: Implementation of the standard library function memcpy. + ** This implementation of memcpy() is ANSI-C89 compatible. + ** + *******************************************************************/ +#pragma once + + +/******************************************************************** + ** Includes for size_t definition + *******************************************************************/ + +#include + + +/******************************************************************** + ** + ** void *memcpy(void *dest, const void *src, size_t count) + ** + ** Args: dest - pointer to destination buffer + ** src - pointer to source buffer + ** count - number of bytes to copy + ** + ** Return: A pointer to destination buffer + ** + ** Purpose: Copies count bytes from src to dest. No overlap check + ** is performed. + ** + *******************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +void *memcpy(void *dest, const void *src, size_t count); + +#ifdef __cplusplus +} +#endif diff --git a/runtime/memset_memmove.S b/runtime/memset_memmove.S new file mode 100644 index 0000000..cb5c2ba --- /dev/null +++ b/runtime/memset_memmove.S @@ -0,0 +1,60 @@ +format elf64 + + +section '.text' executable + +; x64fastcall void* memset(void* dest, int data, size_t count) +; +; Input: (void* rcx, int rdx, size_t r8) +; Output: void* rax +; Clobbers: r10, flags +public memset +memset: + mov rax, rcx + lea r10, [rcx+r8] + +memset_loop: + mov byte[rcx], dl + inc rcx + cmp rcx, r10 + jne memset_loop + + ret + +; x64fastcall void* memmove(void* dest, const void* src, size_t count) +; +; Input: (void* rcx, void* rdx, size_t r8) +; Output: void* rax +; Clobbers: r9, r10, r11, flags +public memmove +memmove: + mov rax, rcx + + lea r10, [rcx+r8] ; past-the-end for *dest + lea r11, [rdx+r8] ; past-the-end for *src + + cmp rdx, rcx + je memmove_end ; return if move buffers are the same + jl memmove_back ; *src < *dest: overlaps, copy backward + +memmove_front: ; *src > *dest: overlaps, copy forward + mov r9b, byte[rdx] + mov byte[rcx], r9b + inc rcx + inc rdx + cmp rcx, r10 + jne memmove_front + + jmp memmove_end + +memmove_back: + dec r10 + dec r11 + mov r9b, byte[r11] + mov byte[r10], r9b + cmp rcx, r10 + jne memmove_back + +memmove_end: + ret + diff --git a/runtime/panic_assert.asm.S b/runtime/panic_assert.asm.S new file mode 100644 index 0000000..858ebbe --- /dev/null +++ b/runtime/panic_assert.asm.S @@ -0,0 +1,13 @@ +format elf64 + + +section '.text' executable + +; whatevercall void __Panic_HaltSystem(); +; +; Panic_HaltSystem halts the system by an infinite loop calling the HLT instruction. +public __Panic_HaltSystem +__Panic_HaltSystem: + hlt + jmp __Panic_HaltSystem + diff --git a/runtime/panic_assert.h b/runtime/panic_assert.h new file mode 100644 index 0000000..1c9f997 --- /dev/null +++ b/runtime/panic_assert.h @@ -0,0 +1,49 @@ +#pragma once + +#ifndef HELOS +#include +#else + +#include "../main.h" +#include "stdio.h" +#include "printf.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define assert(expr) \ + do { \ + if (!(expr)) \ + Panicf("Assertion failed: (" __FILE__ ":%d[%s]), Expression: %s", __LINE__, __func__, #expr); \ + } while (0) + + +// defined in assembly +noreturn void __Panic_HaltSystem(); + + +// Panic() aborts the system after printing the message and some other information. +noreturn inline static void Panic(const char *message) { + io_Printf("Panic: %s\n", message); + __Panic_HaltSystem(); +} + +// Panicf() aborts the system after printing the message using vsnprintf. +noreturn inline static void Panicf(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int ret = vsnprintf(Buffer, HELOS_BUFFER_SIZE, fmt, args); + va_end(args); + io_Printf("Panic: %s\n", Buffer); + __Panic_HaltSystem(); +} + + +#ifdef __cplusplus +} +#endif + +#endif // HELOS diff --git a/runtime/printf.c b/runtime/printf.c new file mode 100644 index 0000000..7a7fda0 --- /dev/null +++ b/runtime/printf.c @@ -0,0 +1,901 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. These routines are thread +// safe and reentrant! +// Use this instead of the bloated standard/newlib printf cause these use +// malloc for printf (and may not be thread safe). +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "printf.h" + + +// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the +// printf_config.h header file +// default: undefined +#ifdef PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + + +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_NTOA_BUFFER_SIZE +#define PRINTF_NTOA_BUFFER_SIZE 32U +#endif + +// 'ftoa' conversion buffer size, this must be big enough to hold one converted +// float number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_FTOA_BUFFER_SIZE +#define PRINTF_FTOA_BUFFER_SIZE 32U +#endif + +// support for the floating point type (%f) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_FLOAT +#define PRINTF_SUPPORT_FLOAT +#endif + +// support for exponential floating point notation (%e/%g) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL +#define PRINTF_SUPPORT_EXPONENTIAL +#endif + +// define the default floating point precision +// default: 6 digits +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6U +#endif + +// define the largest float suitable to print with %f +// default: 1e9 +#ifndef PRINTF_MAX_FLOAT +#define PRINTF_MAX_FLOAT 1e9 +#endif + +// support for the long long types (%llu or %p) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG +#endif + +// support for the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T +#define PRINTF_SUPPORT_PTRDIFF_T +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_LONG (1U << 8U) +#define FLAGS_LONG_LONG (1U << 9U) +#define FLAGS_PRECISION (1U << 10U) +#define FLAGS_ADAPT_EXP (1U << 11U) + + +// import float.h for DBL_MAX +#if defined(PRINTF_SUPPORT_FLOAT) +#include +#endif + + +// output function type +typedef void (*out_fct_type)(char character, void *buffer, size_t idx, size_t maxlen); + + +// wrapper (used as buffer) for output function type +typedef struct { + void (*fct)(char character, void *arg); + void *arg; +} out_fct_wrap_type; + + +// internal buffer output +static inline void _out_buffer(char character, void *buffer, size_t idx, size_t maxlen) { + if (idx < maxlen) { + ((char *)buffer)[idx] = character; + } +} + + +// internal null output +static inline void _out_null(char character, void *buffer, size_t idx, size_t maxlen) { + (void)character; + (void)buffer; + (void)idx; + (void)maxlen; +} + + +// internal _putchar wrapper +static inline void _out_char(char character, void *buffer, size_t idx, size_t maxlen) { + (void)buffer; + (void)idx; + (void)maxlen; + if (character) { + _putchar(character); + } +} + + +// internal output function wrapper +static inline void _out_fct(char character, void *buffer, size_t idx, size_t maxlen) { + (void)idx; + (void)maxlen; + if (character) { + // buffer is the output fct pointer + ((out_fct_wrap_type *)buffer)->fct(character, ((out_fct_wrap_type *)buffer)->arg); + } +} + + +// internal secure strlen +// \return The length of the string (excluding the terminating 0) limited by 'maxsize' +static inline unsigned int _strnlen_s(const char *str, size_t maxsize) { + const char *s; + for (s = str; *s && maxsize--; ++s) + ; + return (unsigned int)(s - str); +} + + +// internal test if char is a digit (0-9) +// \return true if char is a digit +static inline bool _is_digit(char ch) { + return (ch >= '0') && (ch <= '9'); +} + + +// internal ASCII string to unsigned int conversion +static unsigned int _atoi(const char **str) { + unsigned int i = 0U; + while (_is_digit(**str)) { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + + +// output the specified string in reverse, taking care of any zero-padding +static size_t _out_rev(out_fct_type out, char *buffer, size_t idx, size_t maxlen, const char *buf, size_t len, unsigned int width, unsigned int flags) { + const size_t start_idx = idx; + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + // reverse string + while (len) { + out(buf[--len], buffer, idx++, maxlen); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} + + +// internal itoa format +static size_t _ntoa_format(out_fct_type out, char *buffer, size_t idx, size_t maxlen, char *buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags) { + // pad leading zeros + if (!(flags & FLAGS_LEFT)) { + if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + // handle hash + if (flags & FLAGS_HASH) { + if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { + len--; + if (len && (base == 16U)) { + len--; + } + } + if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'x'; + } else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'X'; + } else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'b'; + } + if (len < PRINTF_NTOA_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_NTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +// internal itoa for 'long' type +static size_t _ntoa_long(out_fct_type out, char *buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags) { + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} + + +// internal itoa for 'long long' type +#if defined(PRINTF_SUPPORT_LONG_LONG) +static size_t _ntoa_long_long(out_fct_type out, char *buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) { + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} +#endif // PRINTF_SUPPORT_LONG_LONG + + +#if defined(PRINTF_SUPPORT_FLOAT) + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT +static size_t _etoa(out_fct_type out, char *buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags); +#endif + + +// internal ftoa for fixed decimal floating point +static size_t _ftoa(out_fct_type out, char *buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) { + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + double diff = 0.0; + + // powers of 10 + static const double pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; + + // test for special values + if (value != value) + return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); + if (value < -DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); + if (value > DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); + + // test for very large values + // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad + if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); +#else + return 0U; +#endif + } + + // test for negative + bool negative = false; + if (value < 0) { + negative = true; + value = 0 - value; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { + buf[len++] = '0'; + prec--; + } + + int whole = (int)value; + double tmp = (value - whole) * pow10[prec]; + unsigned long frac = (unsigned long)tmp; + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + if (frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } else if (diff < 0.5) { + } else if ((frac == 0U) || (frac & 1U)) { + // if halfway, round up if odd OR if last digit is 0 + ++frac; + } + + if (prec == 0U) { + diff = value - (double)whole; + if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++whole; + } + } else { + unsigned int count = prec; + // now do fractional part, as an unsigned number + while (len < PRINTF_FTOA_BUFFER_SIZE) { + --count; + buf[len++] = (char)(48U + (frac % 10U)); + if (!(frac /= 10U)) { + break; + } + } + // add extra 0s + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + buf[len++] = '0'; + } + if (len < PRINTF_FTOA_BUFFER_SIZE) { + // add decimal + buf[len++] = '.'; + } + } + + // do whole part, number is reversed + while (len < PRINTF_FTOA_BUFFER_SIZE) { + buf[len++] = (char)(48 + (whole % 10)); + if (!(whole /= 10)) { + break; + } + } + + // pad leading zeros + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_FTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse +static size_t _etoa(out_fct_type out, char *buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) { + // check for NaN and special values + if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { + return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); + } + + // determine the sign + const bool negative = value < 0; + if (negative) { + value = -value; + } + + // default precision + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // determine the decimal exponent + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + union { + uint64_t U; + double F; + } conv; + + conv.F = value; + int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 + conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) + // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 + int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); + // now we want to compute 10^expval but we want to be sure it won't overflow + exp2 = (int)(expval * 3.321928094887362 + 0.5); + const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; + const double z2 = z * z; + conv.U = (uint64_t)(exp2 + 1023) << 52U; + // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + // correct for rounding errors + if (value < conv.F) { + expval--; + conv.F /= 10; + } + + // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters + unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; + + // in "%g" mode, "prec" is the number of *significant figures* not decimals + if (flags & FLAGS_ADAPT_EXP) { + // do we want to fall-back to "%f" mode? + if ((value >= 1e-4) && (value < 1e6)) { + if ((int)prec > expval) { + prec = (unsigned)((int)prec - expval - 1); + } else { + prec = 0; + } + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision + // no characters in exponent + minwidth = 0U; + expval = 0; + } else { + // we use one sigfig for the whole part + if ((prec > 0) && (flags & FLAGS_PRECISION)) { + --prec; + } + } + } + + // will everything fit? + unsigned int fwidth = width; + if (width > minwidth) { + // we didn't fall-back so subtract the characters required for the exponent + fwidth -= minwidth; + } else { + // not enough characters, so go back to default sizing + fwidth = 0U; + } + if ((flags & FLAGS_LEFT) && minwidth) { + // if we're padding on the right, DON'T pad the floating part + fwidth = 0U; + } + + // rescale the float value + if (expval) { + value /= conv.F; + } + + // output the floating part + const size_t start_idx = idx; + idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); + + // output the exponent part + if (minwidth) { + // output the exponential symbol + out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); + // output the exponent value + idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth - 1, FLAGS_ZEROPAD | FLAGS_PLUS); + // might need to right-pad spaces + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); + } + } + return idx; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + + +// internal vsnprintf +static int _vsnprintf(out_fct_type out, char *buffer, const size_t maxlen, const char *format, va_list va) { + unsigned int flags, width, precision, n; + size_t idx = 0U; + + if (!buffer) { + // use null output function + out = _out_null; + } + + while (*format) { + // format specifier? %[flags][width][.precision][length] + if (*format != '%') { + // no + out(*format, buffer, idx++, maxlen); + format++; + continue; + } else { + // yes, evaluate it + format++; + } + + // evaluate flags + flags = 0U; + do { + switch (*format) { + case '0': + flags |= FLAGS_ZEROPAD; + format++; + n = 1U; + break; + case '-': + flags |= FLAGS_LEFT; + format++; + n = 1U; + break; + case '+': + flags |= FLAGS_PLUS; + format++; + n = 1U; + break; + case ' ': + flags |= FLAGS_SPACE; + format++; + n = 1U; + break; + case '#': + flags |= FLAGS_HASH; + format++; + n = 1U; + break; + default: n = 0U; break; + } + } while (n); + + // evaluate width field + width = 0U; + if (_is_digit(*format)) { + width = _atoi(&format); + } else if (*format == '*') { + const int w = va_arg(va, int); + if (w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = (unsigned int)-w; + } else { + width = (unsigned int)w; + } + format++; + } + + // evaluate precision field + precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + format++; + if (_is_digit(*format)) { + precision = _atoi(&format); + } else if (*format == '*') { + const int prec = (int)va_arg(va, int); + precision = prec > 0 ? (unsigned int)prec : 0U; + format++; + } + } + + // evaluate length field + switch (*format) { + case 'l': + flags |= FLAGS_LONG; + format++; + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h': + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't': + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j': + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z': + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default: + break; + } + + // evaluate specifier + switch (*format) { + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + case 'b': { + // set the base + unsigned int base; + if (*format == 'x' || *format == 'X') { + base = 16U; + } else if (*format == 'o') { + base = 8U; + } else if (*format == 'b') { + base = 2U; + } else { + base = 10U; + flags &= ~FLAGS_HASH; // no hash for dec format + } + // uppercase + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + // ignore '0' flag when precision is given + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + // convert the integer + if ((*format == 'i') || (*format == 'd')) { + // signed + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + const long long value = va_arg(va, long long); + idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); +#endif + } else if (flags & FLAGS_LONG) { + const long value = va_arg(va, long); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } else { + const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : + va_arg(va, int); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + } else { + // unsigned + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); +#endif + } else if (flags & FLAGS_LONG) { + idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); + } else { + const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : + va_arg(va, unsigned int); + idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); + } + } + format++; + break; + } +#if defined(PRINTF_SUPPORT_FLOAT) + case 'f': + case 'F': + if (*format == 'F') + flags |= FLAGS_UPPERCASE; + idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + case 'e': + case 'E': + case 'g': + case 'G': + if ((*format == 'g') || (*format == 'G')) + flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E') || (*format == 'G')) + flags |= FLAGS_UPPERCASE; + idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + case 'c': { + unsigned int l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // char output + out((char)va_arg(va, int), buffer, idx++, maxlen); + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 's': { + const char * p = va_arg(va, char *); + unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1); + // pre padding + if (flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // string output + while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { + out(*(p++), buffer, idx++, maxlen); + } + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 'p': { + width = sizeof(void *) * 2U; + flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; +#if defined(PRINTF_SUPPORT_LONG_LONG) + const bool is_ll = sizeof(uintptr_t) == sizeof(long long); + if (is_ll) { + idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void *), false, 16U, precision, width, flags); + } else { +#endif + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void *)), false, 16U, precision, width, flags); +#if defined(PRINTF_SUPPORT_LONG_LONG) + } +#endif + format++; + break; + } + + case '%': + out('%', buffer, idx++, maxlen); + format++; + break; + + default: + out(*format, buffer, idx++, maxlen); + format++; + break; + } + } + + // termination + out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); + + // return written chars without terminating \0 + return (int)idx; +} + + +/////////////////////////////////////////////////////////////////////////////// + +int printf_(const char *format, ...) { + va_list va; + va_start(va, format); + char buffer[1]; + const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + + +int sprintf_(char *buffer, const char *format, ...) { + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + + +int snprintf_(char *buffer, size_t count, const char *format, ...) { + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); + va_end(va); + return ret; +} + + +int vprintf_(const char *format, va_list va) { + char buffer[1]; + return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); +} + + +int vsnprintf_(char *buffer, size_t count, const char *format, va_list va) { + return _vsnprintf(_out_buffer, buffer, count, format, va); +} + + +int fctprintf(void (*out)(char character, void *arg), void *arg, const char *format, ...) { + va_list va; + va_start(va, format); + const out_fct_wrap_type out_fct_wrap = {out, arg}; + const int ret = _vsnprintf(_out_fct, (char *)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); + va_end(va); + return ret; +} diff --git a/runtime/printf.h b/runtime/printf.h new file mode 100644 index 0000000..b0b6e38 --- /dev/null +++ b/runtime/printf.h @@ -0,0 +1,117 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. +// Use this instead of bloated standard/newlib printf. +// These routines are thread safe and reentrant. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _PRINTF_H_ +#define _PRINTF_H_ + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Output a character to a custom device like UART, used by the printf() function + * This function is declared here only. You have to write your custom implementation somewhere + * \param character Character to output + */ +void _putchar(char character); + + +/** + * Tiny printf implementation + * You have to implement _putchar if you use printf() + * To avoid conflicts with the regular printf() API it is overridden by macro defines + * and internal underscore-appended functions like printf_() are used + * \param format A string that specifies the format of the output + * \return The number of characters that are written into the array, not counting the terminating null character + */ +#define printf printf_ +int printf_(const char *format, ...); + + +/** + * Tiny sprintf implementation + * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! + * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! + * \param format A string that specifies the format of the output + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define sprintf sprintf_ +int sprintf_(char *buffer, const char *format, ...); + + +/** + * Tiny snprintf/vsnprintf implementation + * \param buffer A pointer to the buffer where to store the formatted string + * \param count The maximum number of characters to store in the buffer, including a terminating null character + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that COULD have been written into the buffer, not counting the terminating + * null character. A value equal or larger than count indicates truncation. Only when the returned value + * is non-negative and less than count, the string has been completely written. + */ +#define snprintf snprintf_ +#define vsnprintf vsnprintf_ +int snprintf_(char *buffer, size_t count, const char *format, ...); +int vsnprintf_(char *buffer, size_t count, const char *format, va_list va); + + +/** + * Tiny vprintf implementation + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define vprintf vprintf_ +int vprintf_(const char *format, va_list va); + + +/** + * printf with output function + * You may use this as dynamic alternative to printf() with its fixed _putchar() output + * \param out An output function which takes one character and an argument pointer + * \param arg An argument pointer for user data passed to output function + * \param format A string that specifies the format of the output + * \return The number of characters that are sent to the output function, not counting the terminating null character + */ +int fctprintf(void (*out)(char character, void *arg), void *arg, const char *format, ...); + + +#ifdef __cplusplus +} +#endif + + +#endif // _PRINTF_H_ diff --git a/runtime/stdio.c b/runtime/stdio.c new file mode 100644 index 0000000..51cb6bb --- /dev/null +++ b/runtime/stdio.c @@ -0,0 +1,98 @@ + +#include "stdio.h" +#include "unicode.h" +#include "printf.h" +#include "../memory/memory.h" +#include "../graphics/graphics.h" + +#include +#include + + +// printf wants this +void _putchar(char c) { + if (!graphics_Framebuffer) { + UINT16 buf[2] = {c, 0}; + efiStdout->OutputString(efiStdout, buf); + } else { + console_WriteChar(&HelosGraphics_Color_White, c); + if (c == '\n') { // swap buffer on newline + graphics_SwapBuffer(); + } + } +} + +int __io_WriteConsole_bufSize = 256; +UINT16 __io_WriteConsole_bufferReal[256]; +UINT16 *__io_WriteConsole_buffer = __io_WriteConsole_bufferReal; + +void __io_WriteConsole_ResizeBuffer(int size) { + if (__io_WriteConsole_bufSize < size) { + while (__io_WriteConsole_bufSize < size) + __io_WriteConsole_bufSize *= 2; + + DEBUG("allocate -> %d", __io_WriteConsole_bufSize); + + if (__io_WriteConsole_buffer != __io_WriteConsole_bufferReal) + kFree(__io_WriteConsole_buffer); + __io_WriteConsole_buffer = kMalloc(size * sizeof(UINT16)); + } +} + +void io_WriteConsole(const char *str) { + int size = 0; // don't include the \0 at the end here + int len = strlen(str); // left the \0 out here too + + for (int i = 0; + i < len; + i += utf8_Decode(str + i, len - i, NULL), size++) {} + __io_WriteConsole_ResizeBuffer(size + 1); + + uint32_t codepoint; + for (int i = 0, j = 0; + i < len;) { + i += utf8_Decode(str + i, len - i, &codepoint); + __io_WriteConsole_buffer[j++] = codepoint; + } + __io_WriteConsole_buffer[size] = 0; + + if (!graphics_Framebuffer) { + efiStdout->OutputString(efiStdout, __io_WriteConsole_buffer); + } else { + console_WriteUTF16(&HelosGraphics_Color_White, __io_WriteConsole_buffer, 0); + } +} + +void io_WriteConsoleASCII(const char *str) { + if (!graphics_Framebuffer) { + int len = strlen(str); + __io_WriteConsole_ResizeBuffer(len + 1); + for (int i = 0; i <= len; i++) + __io_WriteConsole_buffer[i] = str[i]; + efiStdout->OutputString(efiStdout, __io_WriteConsole_buffer); + } else { + console_WriteASCII(&HelosGraphics_Color_White, str, 0); + } +} + +char __io_Printf_buffer[4096]; + +int io_Printf(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int ret = vsnprintf(__io_Printf_buffer, sizeof(__io_Printf_buffer), fmt, args); + va_end(args); + + io_WriteConsole(__io_Printf_buffer); + + return ret; +} + +EFI_INPUT_KEY io_PauseForKeystroke() { + UINTN index; + EFI_INPUT_KEY key; + efiBootServices->WaitForEvent(1, &efiStdin->WaitForKey, &index); + efiSystemTable->ConIn->ReadKeyStroke(efiSystemTable->ConIn, &key); + + return key; +} diff --git a/runtime/stdio.h b/runtime/stdio.h new file mode 100644 index 0000000..3bbca1b --- /dev/null +++ b/runtime/stdio.h @@ -0,0 +1,40 @@ +#pragma once + +#include "../main.h" + +#include "unicode.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +// io_WriteConsole converts UTF-8 string given to Unicode(BMP only), +// then writing it to the Stdout SIMPLE_TEXT_OUTPUT_INTERFACE or the graphical console. +// +// All \n not preceeded by \r is substituted by \r\n. (WIP) +void io_WriteConsole(const char *str); + +void io_WriteConsoleASCII(const char *str); + +// io_Printf is a printf() replacement, printing to WriteConsole function. +int io_Printf(const char *format, ...); + +EFI_INPUT_KEY io_PauseForKeystroke(); + + +// Debugging printing marcos +#ifndef NDEBUG +#define DEBUG(...) \ + do { \ + printf(__FILE__ ":%d(%s) ", __LINE__, __func__); \ + printf(__VA_ARGS__); \ + } while (0) +#else +#define DEBUG(...) +#endif + + +#ifdef __cplusplus +} +#endif diff --git a/runtime/string.c b/runtime/string.c new file mode 100644 index 0000000..71d1619 --- /dev/null +++ b/runtime/string.c @@ -0,0 +1,10 @@ + +#include + + +size_t strlen(const char *s) { + size_t len = 0; + while (*(s++) != 0) + len++; + return len; +} diff --git a/runtime/test_memmove.c b/runtime/test_memmove.c new file mode 100644 index 0000000..c081f32 --- /dev/null +++ b/runtime/test_memmove.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +__attribute__((ms_abi)) void *memmove(void *, const void *, size_t); +__attribute__((ms_abi)) void *memset(void *, int, size_t); + +void output_memmove(const char *str) { + puts(str); +} + +int main(void) { + char str[] = "1234567890"; + puts(str); + memmove(str + 4, str + 3, 3); // copy from [4,5,6] to [5,6,7] + puts(str); + memmove(str + 3, str + 4, 3); // copy from [4,5,6] to [5,6,7] + puts(str); + memset(str + 3, '0' + 7, 3); + puts(str); + + for (int i = 0; i <= 10; i++) + printf("str[%d]=%d (%c)\n", i, str[i], str[i]); + + // setting effective type of allocated memory to be int + int *p = malloc(3 * sizeof(int)); // allocated memory has no effective type + int arr[3] = {1, 2, 3}; + memmove(p, arr, 3 * sizeof(int)); // allocated memory now has an effective type + printf("%d %d %d\n", p[0], p[1], p[2]); + + // reinterpreting data + double d = 0.1; + // int64_t n = *(int64_t*)(&d); // strict aliasing violation + int64_t n; + memmove(&n, &d, sizeof d); // OK + printf("%a is %" PRIx64 " as an int64_t\n", d, n); +} diff --git a/runtime/test_utf8.c b/runtime/test_utf8.c new file mode 100644 index 0000000..d303fc4 --- /dev/null +++ b/runtime/test_utf8.c @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "Unicode.h" + + +int main(int argc, char *argv[]) { + char str[] = {"天王盖地虎,宝塔镇河妖"}; + int len = strlen(str); + + printf("String: %s, Length: %d\n", str, len); + + uint32_t codepoint, buffer[128]; + int count = 0; + for (int i = 0; + i < len; + i += utf8_Decode(str + i, len - i, buffer + count), count++) {} + + + printf("Count=%d\n", count); + for (int i = 0; i < count; i++) { + printf("Buffer[%d] = %d, ", i, buffer[i]); + char bytes[5] = {}; + utf8_Encode(bytes, buffer[i]); + printf("%s\n", bytes); + } + + return 0; +} diff --git a/runtime/unicode.c b/runtime/unicode.c new file mode 100644 index 0000000..e44ab2f --- /dev/null +++ b/runtime/unicode.c @@ -0,0 +1,84 @@ + +#include "unicode.h" + + +size_t utf8_Decode(const char *utf8, size_t len, uint32_t *codepoint) { + // Some useful precomputed data + static const int trailing[256] = + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; + static const uint32_t offsets[6] = + {0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, 0x82082080}; + + // decode the character + uint32_t output; + int trailingBytes; + + // read the first byte + uint8_t first = *utf8; + trailingBytes = trailing[first]; + if ((first >= HELOS_UTF8_CONTINUATION_MIN && first <= HELOS_UTF8_OVERLONG_LEADER_2) || + (first >= HELOS_UTF8_INVALID_LEADER_MIN && first <= HELOS_UTF8_INVALID_LEADER_MAX) || + trailingBytes + 1 > len) { + // corrupted data or incomplete character + trailingBytes = 0; + if (codepoint != 0) + (*codepoint) = HELOS_UNICODE_ERROR; + } else if (codepoint != 0) { + output = 0; + + // so elegant! + switch (trailingBytes) { + case 5: output += (uint8_t)(*utf8++); output <<= 6; + case 4: output += (uint8_t)(*utf8++); output <<= 6; + case 3: output += (uint8_t)(*utf8++); output <<= 6; + case 2: output += (uint8_t)(*utf8++); output <<= 6; + case 1: output += (uint8_t)(*utf8++); output <<= 6; + case 0: output += (uint8_t)(*utf8++); + } + + (*codepoint) = output - offsets[trailingBytes]; + } + + return trailingBytes + 1; +} + +size_t utf8_EncodeLength(uint32_t codepoint) { + if (codepoint <= 0x007f) // 0000 ~ 007F + return 1; + else if (codepoint <= 0x07ff) // 0080 ~ 07FF + return 2; + else if (codepoint <= 0xffff) // 0800 ~ FFFF + return 3; + else if (codepoint <= 0x10ffff) // 10000 ~ 10FFFF + return 4; + + return 0; // invalid +} + +size_t utf8_Encode(char *utf8, uint32_t codepoint) { + // Some useful precomputed data + static const uint8_t firstBytes[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC}; + + if (codepoint > HELOS_UNICODE_MAX) + codepoint = HELOS_UNICODE_ERROR; // substitute invalid codepoint + + // get the number of bytes to write + size_t len = utf8_EncodeLength(codepoint); + // write the bytes + // so elegant also! + switch (len) { + case 4: utf8[3] = (char)((codepoint | 0x80) & 0xBF); codepoint >>= 6; + case 3: utf8[2] = (char)((codepoint | 0x80) & 0xBF); codepoint >>= 6; + case 2: utf8[1] = (char)((codepoint | 0x80) & 0xBF); codepoint >>= 6; + case 1: utf8[0] = (char)(codepoint | firstBytes[len]); + } + + return len; +} diff --git a/runtime/unicode.h b/runtime/unicode.h new file mode 100644 index 0000000..1c9aa9b --- /dev/null +++ b/runtime/unicode.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + + +// Constants + +#define HELOS_UNICODE_TYPE (uint32_t) // We use uint32_t to hold a Unicode codepoint. + +#define HELOS_UNICODE_ERROR ((uint32_t)0xfffd) // The "error" char or "Unicode replacement character". +#define HELOS_UNICODE_MAX ((uint32_t)0x10ffff) // Maximum valid Unicode codepoint. +#define HELOS_UNICODE_SELF_REPRESENT (0x80) // Chars below SELF_REPRESENT represents themselves in a single byte. + +#define HELOS_UTF8_MAX_BYTES 4 // Maximum number of bytes encoding one unicode point in UTF-8. +#define HELOS_UTF8_CONTINUATION_MIN 0xc0 // Minimum of the continuation range in which bytes are not the first byte of a sequence. +#define HELOS_UTF8_CONTINUATION_MAX 0xbf // Maximum of the continuation range. +#define HELOS_UTF8_OVERLONG_LEADER_1 0xc0 // First leader byte forming an overlong sequence (encoding <=0xff in 2 bytes). +#define HELOS_UTF8_OVERLONG_LEADER_2 0xc1 // Second leader byte forming an overlong sequence. +#define HELOS_UTF8_INVALID_LEADER_MIN 0xf5 // Minimum of the tailing range in which leader bytes form sequences more than 4 bytes long. +#define HELOS_UTF8_INVALID_LEADER_MAX 0xff // Maximum of the tailing range in which leader bytes are invalid. + + +#ifdef __cplusplus +extern "C" { +#endif + + +// utf8_Decode advances the UTF-8 sequence by one character, +// returning the number of bytes advanced. +// +// The codepoint pointer, if not NULL, is set to the decoded value. +// If the Unicode sequence is invalid, the replacement char is returned. +size_t utf8_Decode(const char *utf8, size_t length, uint32_t *codepoint); + +// utf8_EncodeLength returns the number of bytes required to encode +// the given codepoint in UTF-8 (ranging from 1 to 4). +// +// returns 0 if the codepoint is invalid. +size_t utf8_EncodeLength(uint32_t codepoint); + +// utf8_Encode encodes a new character into the UTF-8 buffer, +// if utf8 is not NULL, returning the number of bytes written (max 4). +// +// The buffer must have enough space. +size_t utf8_Encode(char *utf8, uint32_t codepoint); + + +#ifdef __cplusplus +} +#endif diff --git a/util/queue.c b/util/queue.c new file mode 100644 index 0000000..f1f835b --- /dev/null +++ b/util/queue.c @@ -0,0 +1,55 @@ + +#include "queue.h" +#include "../runtime/stdio.h" + + +void queue_InitBuffered(queue *q, void *buffer, uintptr_t size) { + q->data = q->begin = q->end = buffer; + q->size = size; + q->count = 0; +} + +void queue_PushByte(queue *q, const uint8_t b) { + if (q->count == q->size) { // no more space + io_Printf("queue_PushByte: full[%llu bytes], discarding byte 0x%x\n", q->size, b); + return; + } + + q->count++; + *((uint8_t *)(q->end++)) = b; + if (q->end == q->data + q->size) + q->end = q->data; // out of the buffer: wrap around +} + +uint8_t queue_PopByte(queue *q) { + if (q->count == 0) { + io_WriteConsoleASCII("queue_PopByte: poping an empty queue\n"); + return 0; + } + + q->count--; + uint8_t data = *((uint8_t *)(q->begin++)); + if (q->begin == q->data + q->size) + q->begin = q->data; // wrap around + return data; +} + +uint8_t queue_TopByte(queue *q) { + if (q->count == 0) { + io_WriteConsoleASCII("queue_TopByte: accessing an empty queue\n"); + return 0; + } + return *((uint8_t *)q->begin); +} + +bool queue_Empty(queue *q) { + return q->count == 0; +} + +uintptr_t queue_Size(queue *q) { + return q->count; +} + +uintptr_t queue_Space(queue *q) { + return q->size - q->count; +} diff --git a/util/queue.h b/util/queue.h new file mode 100644 index 0000000..6c25b0e --- /dev/null +++ b/util/queue.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../main.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct { + void * data; // the data buffer + uintptr_t size; // size of data buffer + void * begin, *end; // begin and past-the-end for in-queue data + uintptr_t count; // number of in-queue bytes +} queue; + +// initialize a queue with a existing buffer +void queue_InitBuffered(queue *q, void *buffer, uintptr_t size); + +// writes one byte to the queue, discarding if full +void queue_PushByte(queue *q, const uint8_t b); + +// pops one byte from the front of the queue, returning it +uint8_t queue_PopByte(queue *q); + +// return the byte at the front of the queue +uint8_t queue_FrontByte(queue *q); + +// tells if the queue is empty +bool queue_Empty(queue *q); + +// returns the number of bytes in the queue +uintptr_t queue_Size(queue *q); + +// returns the empty space left at the end of the queue +uintptr_t queue_Space(queue *q); + + +#ifdef __cplusplus +} +#endif diff --git a/vterm/vterm.c b/vterm/vterm.c new file mode 100644 index 0000000..ed13bd5 --- /dev/null +++ b/vterm/vterm.c @@ -0,0 +1,2 @@ + +#include "../extlib/libvterm/vterm.h" diff --git a/vterm/vterm.cpp b/vterm/vterm.cpp new file mode 100644 index 0000000..3212515 --- /dev/null +++ b/vterm/vterm.cpp @@ -0,0 +1,13 @@ + +#include "vterm.hpp" + +static void *defaultMalloc(size_t size, void *userdata) { + void *ptr = kMalloc(size); + return ptr; +} + +static void defaultFree(void *ptr, void *userdata) { + kFree(ptr); +} + +VTermAllocatorFunctions vterm_Allocator = {defaultMalloc, defaultFree}; diff --git a/vterm/vterm.hpp b/vterm/vterm.hpp new file mode 100644 index 0000000..e8d6f02 --- /dev/null +++ b/vterm/vterm.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "../main.h" + +#include "../memory/memory.h" +#include "../memory/memory.hpp" +#include "../extlib/libvterm/vterm.h" +#include + + +namespace helos { + + +class Terminal { +public: + Terminal() { + } +}; + + +extern VTermAllocatorFunctions vterm_Allocator; + + +} // namespace helos + +extern "C" { +FASTCALL_ABI void vterm_Init(); +}